titan-agent 5.3.1 → 5.4.0
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 -5
- package/dist/agent/agent.js +11 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/session.js +106 -5
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/subAgent.js +77 -1
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/agent/toolRunner.js +17 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/config/schema.js +18 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/server.js +17 -1
- package/dist/gateway/server.js.map +1 -1
- package/dist/memory/graph.js +49 -15
- package/dist/memory/graph.js.map +1 -1
- package/dist/memory/index.js +192 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory.js +1 -0
- package/dist/memory/memory.js.map +1 -1
- package/dist/organism/drives.js +47 -11
- package/dist/organism/drives.js.map +1 -1
- package/dist/organism/pressure.js +16 -0
- package/dist/organism/pressure.js.map +1 -1
- package/dist/safety/fabricationGuard.js +140 -0
- package/dist/safety/fabricationGuard.js.map +1 -0
- package/dist/skills/builtin/fb_autopilot.js +16 -1
- package/dist/skills/builtin/fb_autopilot.js.map +1 -1
- package/dist/skills/builtin/gepa.js +23 -1
- package/dist/skills/builtin/gepa.js.map +1 -1
- package/dist/skills/builtin/model_trainer.js +31 -4
- package/dist/skills/builtin/model_trainer.js.map +1 -1
- package/dist/skills/builtin/self_improve.js +50 -2
- package/dist/skills/builtin/self_improve.js.map +1 -1
- package/dist/telemetry/activityLog.js +158 -0
- package/dist/telemetry/activityLog.js.map +1 -0
- package/dist/utils/constants.js +3 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/subAgent.ts"],"sourcesContent":["/**\n * TITAN — Universal Sub-Agent\n * Spawns isolated sub-agents that reuse processMessage() with constrained toolsets.\n * Generalizes the swarm.ts pattern into a universal delegation system.\n *\n * Key constraints:\n * - Max depth: 1 (sub-agents cannot spawn sub-sub-agents)\n * - Inherits parent's autonomy mode (can't escalate)\n * - Own stall/loop counters (isolated from parent)\n * - Cost counts toward parent session budget\n */\nimport { chat } from '../providers/router.js';\nimport { executeTools, getToolDefinitions } from './toolRunner.js';\nimport { loadConfig } from '../config/config.js';\nimport type { ChatMessage, ToolDefinition } from '../providers/base.js';\nimport logger from '../utils/logger.js';\nimport { resolveToolsFromCategories, type ToolCategory } from './toolCategories.js';\nimport { registerMailbox, unregisterMailbox, drainMessages, formatMessagesForContext } from './messageBus.js';\nimport { acquireAgent, releaseAgent, createPooledAgent, type PooledAgent } from './agentPool.js';\nimport { getActivePersonaContent } from '../personas/manager.js';\nimport { assembleSystemPrompt } from './systemPromptParts.js';\n\nconst COMPONENT = 'SubAgent';\n\n/** Currently running sub-agent IDs (Set for accurate tracking, prevents counter desync) */\nconst activeSubAgentIds = new Set<string>();\n\nexport interface SubAgentConfig {\n name: string;\n task: string;\n /** Whitelist of tool names this sub-agent can use. Empty = all tools */\n tools?: string[];\n /** Tool categories — resolved to tool names at runtime */\n toolCategories?: ToolCategory[];\n /** Create a git worktree for filesystem isolation */\n useWorktree?: boolean;\n /** Model override (defaults to fast alias) */\n model?: string;\n /** Model tier — resolved via modelAliases (cloud/smart/fast/local) */\n tier?: ModelTier;\n /** System prompt override */\n systemPrompt?: string;\n /** Persona ID to apply (from assets/personas/). Appended to system prompt. */\n persona?: string;\n /** Max tool rounds for this sub-agent (default: 10) */\n maxRounds?: number;\n /** Max tokens per LLM call for this sub-agent (default: from config) */\n maxTokens?: number;\n /** Whether this is being called from within a sub-agent already */\n isNested?: boolean;\n /** Current nesting depth (0 = top-level sub-agent) */\n depth?: number;\n /** Progress callback: called each round with progress info */\n onProgress?: (round: number, totalRounds: number, agentName: string) => void;\n /** Opt-in to agent pool reuse — warm agents preserve context between tasks */\n reusePool?: boolean;\n /** Working directory for filesystem operations (future: scope file tools) */\n workspaceDir?: string;\n /** Tags for observability / filtering */\n tags?: string[];\n /** Stream callbacks for Agent Watcher — tool_call, tool_end, round events */\n streamCallbacks?: {\n onToolCall?: (name: string, args: Record<string, unknown>) => void;\n onToolResult?: (name: string, result: string, durationMs: number, success: boolean) => void;\n onThinking?: () => void;\n onRound?: (round: number, maxRounds: number) => void;\n };\n}\n\nexport interface SubAgentResult {\n content: string;\n toolsUsed: string[];\n success: boolean;\n durationMs: number;\n rounds: number;\n /** Whether the output passed validation checks */\n validated: boolean;\n}\n\n/**\n * Model tier for each sub-agent template.\n * - 'cloud': Heavy reasoning tasks → uses modelAliases.cloud (big cloud model)\n * - 'smart': Complex tasks → uses modelAliases.smart\n * - 'fast': Quick/simple tasks → uses modelAliases.fast (local)\n * - 'local': Must run locally → uses modelAliases.local\n */\nexport type ModelTier = 'cloud' | 'smart' | 'fast' | 'local';\n\n/** Built-in sub-agent templates with mapped personas */\nexport const SUB_AGENT_TEMPLATES: Record<string, Partial<SubAgentConfig> & { tier?: ModelTier }> = {\n explorer: {\n name: 'Explorer',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch', 'browse_url', 'web_read', 'web_act'],\n systemPrompt: `You are the Explorer sub-agent. Your job is to research and gather information from the web using your tools.\n\nAvailable tools and when to use them:\n- web_search: MUST use this first — search for any topic, question, or keyword\n- web_fetch: MUST use after web_search — fetch full page content from the most relevant URLs\n- browse_url: Navigate to a specific URL for interactive content\n- web_read: Extract clean readable text from a URL\n- web_act: Click, scroll, or interact with page elements\n\nMUST rules:\n- MUST call web_search before attempting to answer from memory\n- MUST call web_fetch on at least 2 of the top search result URLs to get full content\n- MUST cross-verify key facts across multiple sources\n\nReturn a structured summary with: key findings, sources (with URLs), and confidence level for main claims.`,\n tier: 'smart',\n },\n coder: {\n name: 'Coder',\n persona: 'incremental-builder',\n tools: ['shell', 'read_file', 'write_file', 'edit_file', 'append_file', 'list_dir', 'code_exec'],\n systemPrompt: `You are the Coder sub-agent. Your job is to WRITE CODE using your tools. Lead with action, not exploration.\n\nCRITICAL RULES:\n- Your FIRST tool call should be write_file (for new files) or read_file (if modifying existing files)\n- Do NOT start with list_dir unless you genuinely don't know the project structure\n- NEVER output code as text — always use write_file or edit_file\n- NEVER describe what you would do — DO IT immediately\n- One sentence of planning max, then CALL THE TOOL\n- After writing files, verify with shell (npm run build, etc.)\n- Keep each edit under 30 lines. For large changes, use multiple edit_file calls\n- Prefer editing existing files over creating new ones\n- No unnecessary comments, error handling, or features beyond scope\n\nTool priority:\n1. write_file — create new files with complete working code\n2. edit_file — modify existing files (read first)\n3. shell — run commands, install packages, verify builds\n4. read_file — only when you need to understand existing code before editing\n5. list_dir — only when you don't know the structure at all\n\nReturn a summary of what was created/modified with exact file paths.`,\n tier: 'smart',\n },\n browser: {\n name: 'Browser',\n persona: 'browser-tester',\n tools: ['browse_url', 'browser_auto_nav', 'browser_search', 'web_read', 'web_act', 'browser_screenshot'],\n systemPrompt: `You are the Browser sub-agent. Your job is to interact with web pages — navigate, extract content, fill forms, and click buttons.\n\nAvailable tools and when to use them:\n- browse_url: MUST use to navigate to a URL before interacting with it\n- browser_screenshot: Take a screenshot to understand the current page state\n- web_read: Extract clean text content from the current page\n- web_act: Click buttons, fill inputs, scroll — use for interactive actions\n- browser_auto_nav: Auto-navigate complex flows (login, multi-step forms)\n- browser_search: Search within a page or site\n\nMUST rules:\n- MUST call browse_url first to open the page\n- MUST call web_read or browser_screenshot to understand page contents before acting\n- MUST report what you found, extracted, or accomplished with exact details\n\nReturn a clear report of what was found/done on the page.`,\n tier: 'fast',\n },\n analyst: {\n name: 'Analyst',\n persona: 'code-reviewer',\n tools: ['web_search', 'web_fetch', 'memory', 'graph_search', 'graph_remember'],\n systemPrompt: `You are the Analyst sub-agent. Your job is to analyze information, identify patterns, and produce structured analytical reports.\n\nAvailable tools and when to use them:\n- web_search: Search for data, statistics, reports, or comparisons\n- web_fetch: Fetch full content from specific URLs for deeper analysis\n- graph_search: Search the knowledge graph for previously stored context\n- graph_remember: Store important findings in the knowledge graph for future reference\n- memory: Store/retrieve key-value data points\n\nMUST rules:\n- MUST call web_search to gather current data before analyzing\n- MUST call graph_search to check for existing relevant context\n- MUST call graph_remember to store key findings after analysis\n- MUST base conclusions on data from tools — not assumptions\n\nReturn a structured analytical report with: executive summary, data findings, patterns identified, confidence levels, and recommendations.`,\n tier: 'cloud',\n },\n researcher: {\n name: 'Researcher',\n persona: 'trend-researcher',\n tools: ['web_search', 'web_read', 'web_fetch', 'rag_search', 'rag_ingest'],\n systemPrompt: `You are the Deep Researcher sub-agent. Your job is to systematically research a question using multiple sources and tools.\n\nAvailable tools and when to use them:\n- web_search: MUST call 2-4 times with different targeted queries to get broad coverage\n- web_fetch: MUST call on the top 3-5 URLs from search results to get full content\n- web_read: Extract clean text from a URL\n- rag_search: Search the local knowledge base for existing research on this topic\n- rag_ingest: Store important findings in the local knowledge base\n\nMethodology — follow in order:\n1. Call rag_search to check for existing research on this topic\n2. Break the question into 2-4 targeted search queries\n3. Call web_search for each query\n4. Call web_fetch on the most relevant URLs (at least 3 total)\n5. Cross-verify key claims across at least 2 independent sources\n6. Call rag_ingest to store important findings\n\nMUST rules:\n- MUST call web_search — never answer research questions from memory\n- MUST call web_fetch to read full content, not just search snippets\n- MUST cite all sources with URLs\n\nOutput format: executive summary → sections with headers → numbered citations [1], [2] → Sources list with URLs.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n // ── Pipeline agents (DeerFlow-inspired) ────────────────────\n reporter: {\n name: 'Reporter',\n persona: 'documentation-writer',\n tools: ['read_file', 'write_file', 'web_fetch'],\n systemPrompt: `You are the Reporter sub-agent. Your job is to synthesize research findings into structured, publication-quality documents saved to disk.\n\nAvailable tools and when to use them:\n- read_file: Read any existing research notes or source files\n- write_file: MUST use to save the final report to disk — NEVER output report content as text\n- web_fetch: Fetch additional content from URLs if needed for a specific section\n\nMUST rules:\n- MUST call write_file to save the report — the output is a file on disk, not inline text\n- MUST call read_file if source material files are referenced in the task\n- If given a file path for the output, MUST save to exactly that path\n\nReport structure: executive summary → sections with markdown headers → confidence levels (High/Medium/Low) per claim → numbered citations → actionable conclusions.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n fact_checker: {\n name: 'Fact Checker',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch'],\n systemPrompt: `You are the Fact Checker sub-agent. Your job is to verify specific claims against multiple independent sources.\n\nAvailable tools and when to use them:\n- web_search: MUST call for each claim to find sources that confirm or refute it\n- web_fetch: MUST call to read the full source content — search snippets are not enough\n\nFor each claim:\n1. Call web_search with 2 different queries targeting this claim\n2. Call web_fetch on the top 2 sources for full content\n3. Compare the claim against what each source actually says\n4. Assign: Verified (3+ sources agree) / Likely (2 sources) / Unverified (1 source) / Disputed (sources conflict) / False (sources contradict)\n\nMUST rules:\n- MUST call web_search for every claim — never verify from memory\n- MUST call web_fetch to read full source content\n\nReturn a structured report: claim → status → evidence → sources used.`,\n maxRounds: 10,\n tier: 'smart',\n },\n // ── Dev agents (TITAN_DEV only) ──────────────────────────\n dev_debugger: {\n name: 'Dev Debugger',\n persona: 'debugger',\n tools: ['shell', 'read_file', 'write_file', 'debug_analyze', 'code_analyze'],\n systemPrompt: `You are the Dev Debugger sub-agent for the TITAN framework. Your job is to find and fix bugs by reading actual code and running diagnostic commands.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source file containing the error before diagnosing\n- code_analyze: Analyze code structure, find potential issues, check for bugs\n- debug_analyze: Deep analysis of error messages, stack traces, and runtime issues\n- shell: Run the failing code, check logs, reproduce the error, verify the fix\n- write_file: Save the fixed code\n\nMUST rules:\n- MUST call read_file to read the actual source code — never diagnose from assumptions\n- MUST call code_analyze or debug_analyze to systematically identify the root cause\n- MUST call shell to reproduce the error before attempting a fix\n- MUST call shell again after the fix to verify it works\n- MUST call write_file to apply the fix — never describe the fix without implementing it\n\nReturn: root cause analysis, fix applied (with file path), verification result.`,\n maxRounds: 15,\n tier: 'smart',\n },\n dev_tester: {\n name: 'Dev Tester',\n persona: 'tdd-engineer',\n tools: ['shell', 'read_file', 'write_file', 'test_generate', 'code_exec'],\n systemPrompt: `You are the Dev Tester sub-agent for the TITAN framework. Your job is to generate, run, and fix tests using vitest.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source code being tested before writing tests\n- test_generate: Generate comprehensive test cases from source code\n- write_file: MUST use to save the test file — never output tests as text\n- shell: Run vitest to execute tests and see results\n- code_exec: Quick isolated code execution for testing snippets\n\nMUST rules:\n- MUST call read_file to understand the code structure before writing tests\n- MUST call write_file to save test files — never output tests inline\n- MUST call shell to run the tests after writing them\n- MUST fix any test failures — don't stop at writing tests\n\nReturn: test file path, number of tests written, test results (pass/fail counts).`,\n maxRounds: 20,\n tier: 'fast',\n },\n dev_reviewer: {\n name: 'Dev Reviewer',\n persona: 'code-reviewer',\n tools: ['shell', 'read_file', 'code_review', 'code_analyze', 'deps_audit'],\n systemPrompt: `You are the Dev Reviewer sub-agent for the TITAN framework. Your job is to perform thorough multi-pass code review.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read each file being reviewed — never review from memory\n- code_analyze: Structural analysis — complexity, patterns, architecture\n- code_review: Deep review — security, logic errors, performance issues\n- deps_audit: Check dependencies for vulnerabilities or outdated packages\n- shell: Run the code, check for type errors, run linter\n\nReview passes (do all):\n1. Security pass: call code_review with security focus\n2. Logic pass: call code_analyze for correctness and edge cases\n3. Performance pass: check for inefficiencies\n4. Dependencies pass: call deps_audit\n\nMUST rules:\n- MUST call read_file for each file reviewed\n- MUST run at least 2 review passes with different focuses\n- Flag real issues only — not style preferences\n\nReturn: structured findings by severity (Critical/Major/Minor), with file + line references.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n dev_architect: {\n name: 'Dev Architect',\n persona: 'backend-architect',\n tools: ['shell', 'read_file', 'write_file', 'code_analyze', 'refactor_suggest', 'doc_generate'],\n systemPrompt: `You are the Dev Architect sub-agent for the TITAN framework. Your job is to analyze system architecture and implement structural improvements.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read source files before proposing architectural changes\n- code_analyze: Analyze codebase structure, dependencies, coupling, and patterns\n- refactor_suggest: Get suggestions for structural improvements and refactoring\n- shell: Run the codebase to understand runtime behavior, check imports, count lines\n- write_file: MUST use to implement changes or create documentation\n- doc_generate: Generate architectural documentation\n\nMUST rules:\n- MUST call read_file and code_analyze before proposing any changes — never guess at structure\n- MUST call shell to understand actual file/folder organization\n- MUST call write_file to implement changes or save documentation — never describe changes inline\n- Think in systems and dependencies, not individual files\n\nReturn: architectural analysis, proposed changes with rationale, implementation summary with file paths.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n};\n\n/**\n * Spawn a sub-agent that runs an isolated agent loop with constrained tools.\n * Returns when the sub-agent completes its task.\n */\nexport async function spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {\n const titanConfig = loadConfig();\n const startTime = Date.now();\n const currentDepth = config.depth ?? 0;\n const subAgentsCfg = (titanConfig as Record<string, unknown>).subAgents as Record<string, unknown> | undefined;\n const maxDepth = (subAgentsCfg?.maxDepth as number) ?? 4; // Increased from 2 → 4 for multi-level task decomposition\n\n // Check depth limit (configurable, default 2)\n if (currentDepth >= maxDepth || config.isNested) {\n return {\n content: `Error: Sub-agent nesting depth limit reached (depth ${currentDepth}/${maxDepth}).`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n // Check concurrency limit\n const maxConcurrent = (titanConfig as Record<string, unknown>).subAgents\n ? ((titanConfig as Record<string, unknown>).subAgents as Record<string, unknown>).maxConcurrent as number || 3\n : 3;\n\n if (activeSubAgentIds.size >= maxConcurrent) {\n return {\n content: `Error: Maximum concurrent sub-agents (${maxConcurrent}) reached. Wait for one to finish.`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n const agentName = config.name || 'SubAgent';\n const agentTrackingId = `${agentName}-${Date.now()}`;\n activeSubAgentIds.add(agentTrackingId);\n // Reduce max rounds by 30% per depth level to prevent runaway nesting\n const baseMaxRounds = config.maxRounds || 10;\n const depthReduction = Math.pow(0.7, currentDepth);\n const maxRounds = Math.max(3, Math.ceil(baseMaxRounds * depthReduction));\n // Model resolution priority:\n // 1. Explicit model passed in config (full model ID like 'ollama/qwen3.5:397b-cloud')\n // 2. Tier from config/template → resolve via modelAliases (cloud/smart/fast/local)\n // 3. subAgents.defaultModel config → resolve as alias name\n // 4. modelAliases.fast fallback\n const aliases = titanConfig.agent.modelAliases || {};\n const subDefaultAlias = (subAgentsCfg?.defaultModel as string) || 'fast';\n const tier = config.tier;\n const model = config.model\n || (tier ? aliases[tier] : undefined)\n || aliases[subDefaultAlias]\n || aliases.fast\n || 'ollama/qwen3.5:cloud';\n\n logger.info(COMPONENT, `Spawning ${agentName}: \"${config.task.slice(0, 80)}...\" (model: ${model}, maxRounds: ${maxRounds})`);\n\n // ── Message Bus: register mailbox for inter-agent communication ──\n registerMailbox(agentName);\n\n // Build tool whitelist\n let availableTools: ToolDefinition[];\n const allTools = getToolDefinitions();\n\n const canNest = currentDepth + 1 < maxDepth; // Allow spawn_agent only if depth allows\n\n // v4.7.0: Hermes-style blocked-for-children tool list. Regardless of\n // template, children never get: spawn_agent, memory_store/write,\n // send_message variants, outbound-publisher tools, or code_exec.\n // Protects against prompt-injection → memory corruption +\n // child-posts-as-Tony side channels.\n let blockedForChildren: Set<string> = new Set();\n try {\n const safety = await import('./subagentSafety.js');\n blockedForChildren = safety.BLOCKED_CHILD_TOOLS;\n } catch { /* optional */ }\n const isChild = currentDepth > 0; // top-level sub-agent = depth 0, but this is the sub-agent itself\n\n if (config.tools && config.tools.length > 0) {\n const toolSet = new Set(config.tools);\n if (!canNest) toolSet.delete('spawn_agent');\n // Ensure send_agent_message is always available for inter-agent comms\n toolSet.add('send_agent_message');\n availableTools = allTools.filter(t => toolSet.has(t.function.name));\n } else {\n availableTools = allTools.filter(t => canNest || t.function.name !== 'spawn_agent');\n }\n\n // v4.7.0: apply blocklist to whatever tools survived template filtering.\n // Primary agent (not a sub-agent) is never filtered. This is the last\n // line of defense — even if a template accidentally includes a\n // dangerous tool, children won't get it.\n if (isChild && blockedForChildren.size > 0) {\n availableTools = availableTools.filter(t => !blockedForChildren.has(t.function.name));\n }\n\n // Build system prompt: TITAN core (minimal) + role template + persona.\n //\n // v4.13 (plan-this-logical-ocean step 4): specialists used to get ONLY\n // the role template, with no TITAN identity / tool-use rules / per-model\n // overlay. On gemma4:31b-cloud this led to specialists hallucinating\n // `<|tool>call:...<|tool|>` markup as text because nothing told them\n // \"use the native tool_calls field, not Gemini's proxy artifact\".\n //\n // Minimal mode gives them: identity, ReAct loop + 3 core rules, tool\n // preference, runtime note, safety, truthfulness, and a per-model\n // overlay. No Delegation block (they don't re-delegate), no Continuous\n // Learning / Memory Tools walls — specialists get a focused task.\n const roleTemplate = config.systemPrompt || `You are the ${agentName} sub-agent of TITAN. Execute the task below using available tools. Be efficient and return a clear summary when done.`;\n const titanCore = assembleSystemPrompt({\n modelId: model,\n persona: config.persona || 'default',\n mode: 'minimal',\n });\n let systemPrompt = `${titanCore}\\n\\n## Role\\n${roleTemplate}`;\n\n // ── Persona: inject persona content from assets/personas/ ──\n const personaId = config.persona;\n if (personaId && personaId !== 'default') {\n try {\n const personaContent = getActivePersonaContent(personaId);\n if (personaContent) {\n systemPrompt += `\\n\\n## Persona: ${personaId}\\n${personaContent}`;\n logger.debug(COMPONENT, `[${agentName}] Applied persona: ${personaId}`);\n }\n } catch {\n logger.debug(COMPONENT, `[${agentName}] Persona \"${personaId}\" not found, using base prompt`);\n }\n }\n\n // ── Agent Pool: try to reuse a warm agent if pool enabled ──\n let pooledAgent: PooledAgent | null = null;\n let messages: ChatMessage[];\n\n if (config.reusePool) {\n const templateName = Object.entries(SUB_AGENT_TEMPLATES).find(\n ([, t]) => t.name === agentName || t.systemPrompt === config.systemPrompt,\n )?.[0] || agentName;\n\n pooledAgent = acquireAgent(templateName, model);\n if (pooledAgent) {\n // Reuse warm agent's conversation history + append new task\n messages = [\n ...pooledAgent.messages,\n { role: 'user', content: config.task },\n ];\n logger.info(COMPONENT, `Reusing pooled agent ${pooledAgent.id} for ${agentName} (${pooledAgent.messages.length} prior messages)`);\n } else {\n // No pooled agent — create fresh and register for later reuse\n pooledAgent = createPooledAgent(templateName, model);\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n } else {\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n\n const toolsUsed: string[] = [];\n let finalContent = '';\n let rounds = 0;\n\n try {\n for (let round = 0; round < maxRounds; round++) {\n rounds = round + 1;\n logger.debug(COMPONENT, `[${agentName}] Round ${rounds}/${maxRounds}`);\n config.onProgress?.(rounds, maxRounds, agentName);\n\n // ── Message Bus: drain incoming messages at start of each round ──\n const incoming = drainMessages(agentName);\n const incomingContext = formatMessagesForContext(incoming);\n if (incomingContext) {\n messages.push({ role: 'system', content: incomingContext });\n logger.debug(COMPONENT, `[${agentName}] Injected ${incoming.length} inter-agent messages`);\n }\n\n // B7: Abort if no tools available — prevents toolless agent from looping uselessly\n if (availableTools.length === 0 && round === 0) {\n logger.warn(COMPONENT, `[${agentName}] No tools available after filtering — aborting`);\n finalContent = `Error: No tools available for sub-agent \"${agentName}\". Check tool permissions and skill configuration.`;\n break;\n }\n\n const response = await chat({\n model,\n messages,\n tools: availableTools.length > 0 ? availableTools : undefined,\n maxTokens: config.maxTokens ?? titanConfig.agent.maxTokens ?? 4096,\n temperature: 0.2,\n });\n\n // No tool calls = done\n if (!response.toolCalls || response.toolCalls.length === 0) {\n finalContent = response.content || 'Task completed.';\n break;\n }\n\n // Process tool calls\n messages.push({\n role: 'assistant',\n content: response.content || '',\n toolCalls: response.toolCalls,\n });\n\n // Emit tool_call events for Agent Watcher\n if (config.streamCallbacks?.onToolCall) {\n for (const tc of response.toolCalls!) { config.streamCallbacks.onToolCall(tc.function.name, JSON.parse(tc.function.arguments || \"{}\")); }\n }\n const toolResults = await executeTools(response.toolCalls);\n // Emit tool_end events for Agent Watcher\n if (config.streamCallbacks?.onToolResult) {\n for (const tr of toolResults) { config.streamCallbacks.onToolResult(tr.name, tr.content, tr.durationMs || 0, tr.success !== false); }\n }\n\n for (const result of toolResults) {\n toolsUsed.push(result.name);\n messages.push({\n role: 'tool',\n content: result.content,\n toolCallId: result.toolCallId,\n name: result.name,\n });\n }\n\n // Last round fallback\n if (round === maxRounds - 1) {\n finalContent = response.content || 'Max rounds reached. Partial results returned.';\n }\n }\n\n const durationMs = Date.now() - startTime;\n logger.info(COMPONENT, `${agentName} completed in ${durationMs}ms (${rounds} rounds, ${toolsUsed.length} tool calls)`);\n\n // Output validation: check for empty, too-short, or error-like responses\n const validated = validateSubAgentOutput(finalContent);\n if (!validated) {\n logger.warn(COMPONENT, `[${agentName}] Output failed validation: \"${finalContent.slice(0, 80)}...\"`);\n }\n\n return {\n content: finalContent,\n toolsUsed: [...new Set(toolsUsed)],\n success: !finalContent.toLowerCase().startsWith('error') && validated,\n durationMs,\n rounds,\n validated,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n logger.error(COMPONENT, `${agentName} failed: ${(err as Error).message}`);\n return {\n content: `Sub-agent error: ${(err as Error).message}`,\n toolsUsed: [...new Set(toolsUsed)],\n success: false,\n durationMs,\n rounds,\n validated: false,\n };\n } finally {\n activeSubAgentIds.delete(agentTrackingId);\n\n // ── Message Bus: unregister mailbox on completion ──\n unregisterMailbox(agentName);\n\n // ── Agent Pool: release back to pool for future reuse ──\n if (config.reusePool && pooledAgent) {\n releaseAgent(pooledAgent.id, messages, toolsUsed, rounds);\n }\n }\n}\n\n/** Validate sub-agent output for quality */\nfunction validateSubAgentOutput(content: string): boolean {\n if (!content || content.trim().length < 20) return false;\n const lower = content.toLowerCase();\n if (lower.startsWith('i cannot') || lower.startsWith('i\\'m unable') || lower.startsWith('i am unable')) return false;\n if (lower.startsWith('error:') || lower.startsWith('sub-agent error:')) return false;\n if (lower === 'task completed.' && content.length < 20) return false;\n return true;\n}\n\n/** Get count of currently active sub-agents */\nexport function getActiveSubAgentCount(): number {\n return activeSubAgentIds.size;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/subAgent.ts"],"sourcesContent":["/**\n * TITAN — Universal Sub-Agent\n * Spawns isolated sub-agents that reuse processMessage() with constrained toolsets.\n * Generalizes the swarm.ts pattern into a universal delegation system.\n *\n * Key constraints:\n * - Max depth: 1 (sub-agents cannot spawn sub-sub-agents)\n * - Inherits parent's autonomy mode (can't escalate)\n * - Own stall/loop counters (isolated from parent)\n * - Cost counts toward parent session budget\n */\nimport { chat } from '../providers/router.js';\nimport { executeTools, getToolDefinitions, type ToolResult } from './toolRunner.js';\nimport { loadConfig } from '../config/config.js';\nimport type { ChatMessage, ToolDefinition } from '../providers/base.js';\nimport logger from '../utils/logger.js';\nimport { resolveToolsFromCategories, type ToolCategory } from './toolCategories.js';\nimport { registerMailbox, unregisterMailbox, drainMessages, formatMessagesForContext } from './messageBus.js';\nimport { acquireAgent, releaseAgent, createPooledAgent, type PooledAgent } from './agentPool.js';\nimport { getActivePersonaContent } from '../personas/manager.js';\nimport { assembleSystemPrompt } from './systemPromptParts.js';\n\nconst COMPONENT = 'SubAgent';\n\n/** Currently running sub-agent IDs (Set for accurate tracking, prevents counter desync) */\nconst activeSubAgentIds = new Set<string>();\n\nexport interface SubAgentConfig {\n name: string;\n task: string;\n /** Whitelist of tool names this sub-agent can use. Empty = all tools */\n tools?: string[];\n /** Tool categories — resolved to tool names at runtime */\n toolCategories?: ToolCategory[];\n /** Create a git worktree for filesystem isolation */\n useWorktree?: boolean;\n /** Model override (defaults to fast alias) */\n model?: string;\n /** Model tier — resolved via modelAliases (cloud/smart/fast/local) */\n tier?: ModelTier;\n /** System prompt override */\n systemPrompt?: string;\n /** Persona ID to apply (from assets/personas/). Appended to system prompt. */\n persona?: string;\n /** Max tool rounds for this sub-agent (default: 10) */\n maxRounds?: number;\n /** Max tokens per LLM call for this sub-agent (default: from config) */\n maxTokens?: number;\n /** Whether this is being called from within a sub-agent already */\n isNested?: boolean;\n /** Current nesting depth (0 = top-level sub-agent) */\n depth?: number;\n /** Progress callback: called each round with progress info */\n onProgress?: (round: number, totalRounds: number, agentName: string) => void;\n /** Opt-in to agent pool reuse — warm agents preserve context between tasks */\n reusePool?: boolean;\n /** Working directory for filesystem operations (future: scope file tools) */\n workspaceDir?: string;\n /** Tags for observability / filtering */\n tags?: string[];\n /** Stream callbacks for Agent Watcher — tool_call, tool_end, round events */\n streamCallbacks?: {\n onToolCall?: (name: string, args: Record<string, unknown>) => void;\n onToolResult?: (name: string, result: string, durationMs: number, success: boolean) => void;\n onThinking?: () => void;\n onRound?: (round: number, maxRounds: number) => void;\n };\n}\n\nexport interface SubAgentResult {\n content: string;\n toolsUsed: string[];\n success: boolean;\n durationMs: number;\n rounds: number;\n /** Whether the output passed validation checks */\n validated: boolean;\n}\n\n/**\n * Model tier for each sub-agent template.\n * - 'cloud': Heavy reasoning tasks → uses modelAliases.cloud (big cloud model)\n * - 'smart': Complex tasks → uses modelAliases.smart\n * - 'fast': Quick/simple tasks → uses modelAliases.fast (local)\n * - 'local': Must run locally → uses modelAliases.local\n */\nexport type ModelTier = 'cloud' | 'smart' | 'fast' | 'local';\n\n/** Built-in sub-agent templates with mapped personas */\nexport const SUB_AGENT_TEMPLATES: Record<string, Partial<SubAgentConfig> & { tier?: ModelTier }> = {\n explorer: {\n name: 'Explorer',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch', 'browse_url', 'web_read', 'web_act'],\n systemPrompt: `You are the Explorer sub-agent. Your job is to research and gather information from the web using your tools.\n\nAvailable tools and when to use them:\n- web_search: MUST use this first — search for any topic, question, or keyword\n- web_fetch: MUST use after web_search — fetch full page content from the most relevant URLs\n- browse_url: Navigate to a specific URL for interactive content\n- web_read: Extract clean readable text from a URL\n- web_act: Click, scroll, or interact with page elements\n\nMUST rules:\n- MUST call web_search before attempting to answer from memory\n- MUST call web_fetch on at least 2 of the top search result URLs to get full content\n- MUST cross-verify key facts across multiple sources\n\nReturn a structured summary with: key findings, sources (with URLs), and confidence level for main claims.`,\n tier: 'smart',\n },\n coder: {\n name: 'Coder',\n persona: 'incremental-builder',\n tools: ['shell', 'read_file', 'write_file', 'edit_file', 'append_file', 'list_dir', 'code_exec'],\n systemPrompt: `You are the Coder sub-agent. Your job is to WRITE CODE using your tools. Lead with action, not exploration.\n\nCRITICAL RULES:\n- Your FIRST tool call should be write_file (for new files) or read_file (if modifying existing files)\n- Do NOT start with list_dir unless you genuinely don't know the project structure\n- NEVER output code as text — always use write_file or edit_file\n- NEVER describe what you would do — DO IT immediately\n- One sentence of planning max, then CALL THE TOOL\n- After writing files, verify with shell (npm run build, etc.)\n- Keep each edit under 30 lines. For large changes, use multiple edit_file calls\n- Prefer editing existing files over creating new ones\n- No unnecessary comments, error handling, or features beyond scope\n\nTool priority:\n1. write_file — create new files with complete working code\n2. edit_file — modify existing files (read first)\n3. shell — run commands, install packages, verify builds\n4. read_file — only when you need to understand existing code before editing\n5. list_dir — only when you don't know the structure at all\n\nReturn a summary of what was created/modified with exact file paths.`,\n tier: 'smart',\n },\n browser: {\n name: 'Browser',\n persona: 'browser-tester',\n tools: ['browse_url', 'browser_auto_nav', 'browser_search', 'web_read', 'web_act', 'browser_screenshot'],\n systemPrompt: `You are the Browser sub-agent. Your job is to interact with web pages — navigate, extract content, fill forms, and click buttons.\n\nAvailable tools and when to use them:\n- browse_url: MUST use to navigate to a URL before interacting with it\n- browser_screenshot: Take a screenshot to understand the current page state\n- web_read: Extract clean text content from the current page\n- web_act: Click buttons, fill inputs, scroll — use for interactive actions\n- browser_auto_nav: Auto-navigate complex flows (login, multi-step forms)\n- browser_search: Search within a page or site\n\nMUST rules:\n- MUST call browse_url first to open the page\n- MUST call web_read or browser_screenshot to understand page contents before acting\n- MUST report what you found, extracted, or accomplished with exact details\n\nReturn a clear report of what was found/done on the page.`,\n tier: 'fast',\n },\n analyst: {\n name: 'Analyst',\n persona: 'code-reviewer',\n tools: ['web_search', 'web_fetch', 'memory', 'graph_search', 'graph_remember'],\n systemPrompt: `You are the Analyst sub-agent. Your job is to analyze information, identify patterns, and produce structured analytical reports.\n\nAvailable tools and when to use them:\n- web_search: Search for data, statistics, reports, or comparisons\n- web_fetch: Fetch full content from specific URLs for deeper analysis\n- graph_search: Search the knowledge graph for previously stored context\n- graph_remember: Store important findings in the knowledge graph for future reference\n- memory: Store/retrieve key-value data points\n\nMUST rules:\n- MUST call web_search to gather current data before analyzing\n- MUST call graph_search to check for existing relevant context\n- MUST call graph_remember to store key findings after analysis\n- MUST base conclusions on data from tools — not assumptions\n\nReturn a structured analytical report with: executive summary, data findings, patterns identified, confidence levels, and recommendations.`,\n tier: 'cloud',\n },\n researcher: {\n name: 'Researcher',\n persona: 'trend-researcher',\n tools: ['web_search', 'web_read', 'web_fetch', 'rag_search', 'rag_ingest'],\n systemPrompt: `You are the Deep Researcher sub-agent. Your job is to systematically research a question using multiple sources and tools.\n\nAvailable tools and when to use them:\n- web_search: MUST call 2-4 times with different targeted queries to get broad coverage\n- web_fetch: MUST call on the top 3-5 URLs from search results to get full content\n- web_read: Extract clean text from a URL\n- rag_search: Search the local knowledge base for existing research on this topic\n- rag_ingest: Store important findings in the local knowledge base\n\nMethodology — follow in order:\n1. Call rag_search to check for existing research on this topic\n2. Break the question into 2-4 targeted search queries\n3. Call web_search for each query\n4. Call web_fetch on the most relevant URLs (at least 3 total)\n5. Cross-verify key claims across at least 2 independent sources\n6. Call rag_ingest to store important findings\n\nMUST rules:\n- MUST call web_search — never answer research questions from memory\n- MUST call web_fetch to read full content, not just search snippets\n- MUST cite all sources with URLs\n\nOutput format: executive summary → sections with headers → numbered citations [1], [2] → Sources list with URLs.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n // ── Pipeline agents (DeerFlow-inspired) ────────────────────\n reporter: {\n name: 'Reporter',\n persona: 'documentation-writer',\n tools: ['read_file', 'write_file', 'web_fetch'],\n systemPrompt: `You are the Reporter sub-agent. Your job is to synthesize research findings into structured, publication-quality documents saved to disk.\n\nAvailable tools and when to use them:\n- read_file: Read any existing research notes or source files\n- write_file: MUST use to save the final report to disk — NEVER output report content as text\n- web_fetch: Fetch additional content from URLs if needed for a specific section\n\nMUST rules:\n- MUST call write_file to save the report — the output is a file on disk, not inline text\n- MUST call read_file if source material files are referenced in the task\n- If given a file path for the output, MUST save to exactly that path\n\nReport structure: executive summary → sections with markdown headers → confidence levels (High/Medium/Low) per claim → numbered citations → actionable conclusions.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n fact_checker: {\n name: 'Fact Checker',\n persona: 'context-engineer',\n tools: ['web_search', 'web_fetch'],\n systemPrompt: `You are the Fact Checker sub-agent. Your job is to verify specific claims against multiple independent sources.\n\nAvailable tools and when to use them:\n- web_search: MUST call for each claim to find sources that confirm or refute it\n- web_fetch: MUST call to read the full source content — search snippets are not enough\n\nFor each claim:\n1. Call web_search with 2 different queries targeting this claim\n2. Call web_fetch on the top 2 sources for full content\n3. Compare the claim against what each source actually says\n4. Assign: Verified (3+ sources agree) / Likely (2 sources) / Unverified (1 source) / Disputed (sources conflict) / False (sources contradict)\n\nMUST rules:\n- MUST call web_search for every claim — never verify from memory\n- MUST call web_fetch to read full source content\n\nReturn a structured report: claim → status → evidence → sources used.`,\n maxRounds: 10,\n tier: 'smart',\n },\n // ── Dev agents (TITAN_DEV only) ──────────────────────────\n dev_debugger: {\n name: 'Dev Debugger',\n persona: 'debugger',\n tools: ['shell', 'read_file', 'write_file', 'debug_analyze', 'code_analyze'],\n systemPrompt: `You are the Dev Debugger sub-agent for the TITAN framework. Your job is to find and fix bugs by reading actual code and running diagnostic commands.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source file containing the error before diagnosing\n- code_analyze: Analyze code structure, find potential issues, check for bugs\n- debug_analyze: Deep analysis of error messages, stack traces, and runtime issues\n- shell: Run the failing code, check logs, reproduce the error, verify the fix\n- write_file: Save the fixed code\n\nMUST rules:\n- MUST call read_file to read the actual source code — never diagnose from assumptions\n- MUST call code_analyze or debug_analyze to systematically identify the root cause\n- MUST call shell to reproduce the error before attempting a fix\n- MUST call shell again after the fix to verify it works\n- MUST call write_file to apply the fix — never describe the fix without implementing it\n\nReturn: root cause analysis, fix applied (with file path), verification result.`,\n maxRounds: 15,\n tier: 'smart',\n },\n dev_tester: {\n name: 'Dev Tester',\n persona: 'tdd-engineer',\n tools: ['shell', 'read_file', 'write_file', 'test_generate', 'code_exec'],\n systemPrompt: `You are the Dev Tester sub-agent for the TITAN framework. Your job is to generate, run, and fix tests using vitest.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read the source code being tested before writing tests\n- test_generate: Generate comprehensive test cases from source code\n- write_file: MUST use to save the test file — never output tests as text\n- shell: Run vitest to execute tests and see results\n- code_exec: Quick isolated code execution for testing snippets\n\nMUST rules:\n- MUST call read_file to understand the code structure before writing tests\n- MUST call write_file to save test files — never output tests inline\n- MUST call shell to run the tests after writing them\n- MUST fix any test failures — don't stop at writing tests\n\nReturn: test file path, number of tests written, test results (pass/fail counts).`,\n maxRounds: 20,\n tier: 'fast',\n },\n dev_reviewer: {\n name: 'Dev Reviewer',\n persona: 'code-reviewer',\n tools: ['shell', 'read_file', 'code_review', 'code_analyze', 'deps_audit'],\n systemPrompt: `You are the Dev Reviewer sub-agent for the TITAN framework. Your job is to perform thorough multi-pass code review.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read each file being reviewed — never review from memory\n- code_analyze: Structural analysis — complexity, patterns, architecture\n- code_review: Deep review — security, logic errors, performance issues\n- deps_audit: Check dependencies for vulnerabilities or outdated packages\n- shell: Run the code, check for type errors, run linter\n\nReview passes (do all):\n1. Security pass: call code_review with security focus\n2. Logic pass: call code_analyze for correctness and edge cases\n3. Performance pass: check for inefficiencies\n4. Dependencies pass: call deps_audit\n\nMUST rules:\n- MUST call read_file for each file reviewed\n- MUST run at least 2 review passes with different focuses\n- Flag real issues only — not style preferences\n\nReturn: structured findings by severity (Critical/Major/Minor), with file + line references.`,\n maxRounds: 10,\n tier: 'cloud',\n },\n dev_architect: {\n name: 'Dev Architect',\n persona: 'backend-architect',\n tools: ['shell', 'read_file', 'write_file', 'code_analyze', 'refactor_suggest', 'doc_generate'],\n systemPrompt: `You are the Dev Architect sub-agent for the TITAN framework. Your job is to analyze system architecture and implement structural improvements.\n\nAvailable tools and when to use them:\n- read_file: MUST use to read source files before proposing architectural changes\n- code_analyze: Analyze codebase structure, dependencies, coupling, and patterns\n- refactor_suggest: Get suggestions for structural improvements and refactoring\n- shell: Run the codebase to understand runtime behavior, check imports, count lines\n- write_file: MUST use to implement changes or create documentation\n- doc_generate: Generate architectural documentation\n\nMUST rules:\n- MUST call read_file and code_analyze before proposing any changes — never guess at structure\n- MUST call shell to understand actual file/folder organization\n- MUST call write_file to implement changes or save documentation — never describe changes inline\n- Think in systems and dependencies, not individual files\n\nReturn: architectural analysis, proposed changes with rationale, implementation summary with file paths.`,\n maxRounds: 15,\n tier: 'cloud',\n },\n};\n\n/**\n * Spawn a sub-agent that runs an isolated agent loop with constrained tools.\n * Returns when the sub-agent completes its task.\n */\nexport async function spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {\n const titanConfig = loadConfig();\n const startTime = Date.now();\n const currentDepth = config.depth ?? 0;\n const subAgentsCfg = (titanConfig as Record<string, unknown>).subAgents as Record<string, unknown> | undefined;\n const maxDepth = (subAgentsCfg?.maxDepth as number) ?? 4; // Increased from 2 → 4 for multi-level task decomposition\n\n // Check depth limit (configurable, default 2)\n if (currentDepth >= maxDepth || config.isNested) {\n return {\n content: `Error: Sub-agent nesting depth limit reached (depth ${currentDepth}/${maxDepth}).`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n // Check concurrency limit\n const maxConcurrent = (titanConfig as Record<string, unknown>).subAgents\n ? ((titanConfig as Record<string, unknown>).subAgents as Record<string, unknown>).maxConcurrent as number || 3\n : 3;\n\n if (activeSubAgentIds.size >= maxConcurrent) {\n return {\n content: `Error: Maximum concurrent sub-agents (${maxConcurrent}) reached. Wait for one to finish.`,\n toolsUsed: [],\n success: false,\n durationMs: 0,\n rounds: 0,\n validated: false,\n };\n }\n\n const agentName = config.name || 'SubAgent';\n const agentTrackingId = `${agentName}-${Date.now()}`;\n activeSubAgentIds.add(agentTrackingId);\n\n // Phase 8: log agent spawn\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_spawn', agent: agentName, task: config.task.slice(0, 200) });\n } catch { /* non-critical */ }\n // Reduce max rounds by 30% per depth level to prevent runaway nesting\n const baseMaxRounds = config.maxRounds || 10;\n const depthReduction = Math.pow(0.7, currentDepth);\n const maxRounds = Math.max(3, Math.ceil(baseMaxRounds * depthReduction));\n // Model resolution priority:\n // 1. Explicit model passed in config (full model ID like 'ollama/qwen3.5:397b-cloud')\n // 2. Tier from config/template → resolve via modelAliases (cloud/smart/fast/local)\n // 3. subAgents.defaultModel config → resolve as alias name\n // 4. modelAliases.fast fallback\n const aliases = titanConfig.agent.modelAliases || {};\n const subDefaultAlias = (subAgentsCfg?.defaultModel as string) || 'fast';\n const tier = config.tier;\n const model = config.model\n || (tier ? aliases[tier] : undefined)\n || aliases[subDefaultAlias]\n || aliases.fast\n || 'ollama/qwen3.5:cloud';\n\n logger.info(COMPONENT, `Spawning ${agentName}: \"${config.task.slice(0, 80)}...\" (model: ${model}, maxRounds: ${maxRounds})`);\n\n // ── Message Bus: register mailbox for inter-agent communication ──\n registerMailbox(agentName);\n\n // Build tool whitelist\n let availableTools: ToolDefinition[];\n const allTools = getToolDefinitions();\n\n const canNest = currentDepth + 1 < maxDepth; // Allow spawn_agent only if depth allows\n\n // v4.7.0: Hermes-style blocked-for-children tool list. Regardless of\n // template, children never get: spawn_agent, memory_store/write,\n // send_message variants, outbound-publisher tools, or code_exec.\n // Protects against prompt-injection → memory corruption +\n // child-posts-as-Tony side channels.\n let blockedForChildren: Set<string> = new Set();\n try {\n const safety = await import('./subagentSafety.js');\n blockedForChildren = safety.BLOCKED_CHILD_TOOLS;\n } catch { /* optional */ }\n const isChild = currentDepth > 0; // top-level sub-agent = depth 0, but this is the sub-agent itself\n\n if (config.tools && config.tools.length > 0) {\n const toolSet = new Set(config.tools);\n if (!canNest) toolSet.delete('spawn_agent');\n // Ensure send_agent_message is always available for inter-agent comms\n toolSet.add('send_agent_message');\n availableTools = allTools.filter(t => toolSet.has(t.function.name));\n } else {\n availableTools = allTools.filter(t => canNest || t.function.name !== 'spawn_agent');\n }\n\n // v4.7.0: apply blocklist to whatever tools survived template filtering.\n // Primary agent (not a sub-agent) is never filtered. This is the last\n // line of defense — even if a template accidentally includes a\n // dangerous tool, children won't get it.\n if (isChild && blockedForChildren.size > 0) {\n availableTools = availableTools.filter(t => !blockedForChildren.has(t.function.name));\n }\n\n // Build system prompt: TITAN core (minimal) + role template + persona.\n //\n // v4.13 (plan-this-logical-ocean step 4): specialists used to get ONLY\n // the role template, with no TITAN identity / tool-use rules / per-model\n // overlay. On gemma4:31b-cloud this led to specialists hallucinating\n // `<|tool>call:...<|tool|>` markup as text because nothing told them\n // \"use the native tool_calls field, not Gemini's proxy artifact\".\n //\n // Minimal mode gives them: identity, ReAct loop + 3 core rules, tool\n // preference, runtime note, safety, truthfulness, and a per-model\n // overlay. No Delegation block (they don't re-delegate), no Continuous\n // Learning / Memory Tools walls — specialists get a focused task.\n const roleTemplate = config.systemPrompt || `You are the ${agentName} sub-agent of TITAN. Execute the task below using available tools. Be efficient and return a clear summary when done.`;\n const titanCore = assembleSystemPrompt({\n modelId: model,\n persona: config.persona || 'default',\n mode: 'minimal',\n });\n let systemPrompt = `${titanCore}\\n\\n## Role\\n${roleTemplate}`;\n\n // ── Persona: inject persona content from assets/personas/ ──\n const personaId = config.persona;\n if (personaId && personaId !== 'default') {\n try {\n const personaContent = getActivePersonaContent(personaId);\n if (personaContent) {\n systemPrompt += `\\n\\n## Persona: ${personaId}\\n${personaContent}`;\n logger.debug(COMPONENT, `[${agentName}] Applied persona: ${personaId}`);\n }\n } catch {\n logger.debug(COMPONENT, `[${agentName}] Persona \"${personaId}\" not found, using base prompt`);\n }\n }\n\n // ── Agent Pool: try to reuse a warm agent if pool enabled ──\n let pooledAgent: PooledAgent | null = null;\n let messages: ChatMessage[];\n\n if (config.reusePool) {\n const templateName = Object.entries(SUB_AGENT_TEMPLATES).find(\n ([, t]) => t.name === agentName || t.systemPrompt === config.systemPrompt,\n )?.[0] || agentName;\n\n pooledAgent = acquireAgent(templateName, model);\n if (pooledAgent) {\n // Reuse warm agent's conversation history + append new task\n messages = [\n ...pooledAgent.messages,\n { role: 'user', content: config.task },\n ];\n logger.info(COMPONENT, `Reusing pooled agent ${pooledAgent.id} for ${agentName} (${pooledAgent.messages.length} prior messages)`);\n } else {\n // No pooled agent — create fresh and register for later reuse\n pooledAgent = createPooledAgent(templateName, model);\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n } else {\n messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: config.task },\n ];\n }\n\n const toolsUsed: string[] = [];\n let finalContent = '';\n let rounds = 0;\n\n // Phase 9: safety state tracking\n const toolHistory: Array<{ name: string; args: string; round: number }> = [];\n let lastContent = '';\n let stallCount = 0;\n const STALL_THRESHOLD = 3;\n const LOOP_THRESHOLD = 2;\n\n try {\n for (let round = 0; round < maxRounds; round++) {\n rounds = round + 1;\n logger.debug(COMPONENT, `[${agentName}] Round ${rounds}/${maxRounds}`);\n config.onProgress?.(rounds, maxRounds, agentName);\n\n // ── Message Bus: drain incoming messages at start of each round ──\n const incoming = drainMessages(agentName);\n const incomingContext = formatMessagesForContext(incoming);\n if (incomingContext) {\n messages.push({ role: 'system', content: incomingContext });\n logger.debug(COMPONENT, `[${agentName}] Injected ${incoming.length} inter-agent messages`);\n }\n\n // B7: Abort if no tools available — prevents toolless agent from looping uselessly\n if (availableTools.length === 0 && round === 0) {\n logger.warn(COMPONENT, `[${agentName}] No tools available after filtering — aborting`);\n finalContent = `Error: No tools available for sub-agent \"${agentName}\". Check tool permissions and skill configuration.`;\n break;\n }\n\n const response = await chat({\n model,\n messages,\n tools: availableTools.length > 0 ? availableTools : undefined,\n maxTokens: config.maxTokens ?? titanConfig.agent.maxTokens ?? 4096,\n temperature: 0.2,\n });\n\n // No tool calls = done\n if (!response.toolCalls || response.toolCalls.length === 0) {\n finalContent = response.content || 'Task completed.';\n break;\n }\n\n // Process tool calls\n messages.push({\n role: 'assistant',\n content: response.content || '',\n toolCalls: response.toolCalls,\n });\n\n // Emit tool_call events for Agent Watcher\n if (config.streamCallbacks?.onToolCall) {\n for (const tc of response.toolCalls!) { config.streamCallbacks.onToolCall(tc.function.name, JSON.parse(tc.function.arguments || \"{}\")); }\n }\n // Phase 9: per-tool error handling — one failing tool must not kill the session\n const toolResults: ToolResult[] = [];\n let allToolsFailed = true;\n for (const tc of response.toolCalls!) {\n let result: ToolResult;\n try {\n const singleResult = await executeTools([tc]);\n result = singleResult[0];\n if (result.success !== false) allToolsFailed = false;\n } catch (toolErr) {\n result = {\n toolCallId: tc.id,\n name: tc.function.name,\n content: `Error executing ${tc.function.name}: ${(toolErr as Error).message}`,\n success: false,\n durationMs: 0,\n };\n logger.warn(COMPONENT, `[${agentName}] Tool ${tc.function.name} failed: ${(toolErr as Error).message}`);\n }\n // Phase 9: Summarize tool outputs >10K chars to prevent context bloat\n const MAX_TOOL_OUTPUT = 10_000;\n if (result.content && result.content.length > MAX_TOOL_OUTPUT) {\n const originalLen = result.content.length;\n const marker = `\\n\\n[…output truncated from ${originalLen} to ${MAX_TOOL_OUTPUT} chars — full result available via tool re-execution with narrower scope]`;\n result.content = result.content.slice(0, MAX_TOOL_OUTPUT - marker.length) + marker;\n }\n toolResults.push(result);\n toolHistory.push({ name: result.name, args: tc.function.arguments || '{}', round });\n }\n\n // Phase 9: Graceful degradation — if every tool in a round fails, bail early\n if (allToolsFailed && toolResults.length > 0) {\n const failures = toolResults.map(r => `${r.name}: ${r.content.slice(0, 120)}`).join('; ');\n finalContent = `Error: All ${toolResults.length} tool(s) failed in round ${rounds}. ${failures}`;\n logger.warn(COMPONENT, `[${agentName}] All tools failed — aborting after ${rounds} rounds`);\n break;\n }\n\n // Emit tool_end events for Agent Watcher\n if (config.streamCallbacks?.onToolResult) {\n for (const tr of toolResults) { config.streamCallbacks.onToolResult(tr.name, tr.content, tr.durationMs || 0, tr.success !== false); }\n }\n\n for (const result of toolResults) {\n toolsUsed.push(result.name);\n messages.push({\n role: 'tool',\n content: result.content,\n toolCallId: result.toolCallId,\n name: result.name,\n });\n }\n\n // Phase 9: Stall detection — if content hasn't changed meaningfully, count it\n const currentContent = response.content || '';\n if (currentContent && currentContent === lastContent) {\n stallCount++;\n logger.warn(COMPONENT, `[${agentName}] Stall detected (${stallCount}/${STALL_THRESHOLD}) — identical content in round ${rounds}`);\n if (stallCount >= STALL_THRESHOLD) {\n finalContent = `Task stalled after ${rounds} rounds — the agent repeated the same reasoning without progress.`;\n logger.warn(COMPONENT, `[${agentName}] Aborting due to stall`);\n break;\n }\n } else {\n stallCount = 0;\n lastContent = currentContent;\n }\n\n // Phase 9: Loop detection — same tool+args repeated\n if (toolHistory.length >= 2) {\n const last = toolHistory[toolHistory.length - 1];\n const prev = toolHistory[toolHistory.length - 2];\n if (last.name === prev.name && last.args === prev.args) {\n logger.warn(COMPONENT, `[${agentName}] Loop detected — ${last.name} called with identical args in consecutive rounds`);\n finalContent = `Task looped after ${rounds} rounds — the agent called ${last.name} repeatedly with the same arguments.`;\n break;\n }\n }\n\n // Last round fallback\n if (round === maxRounds - 1) {\n finalContent = response.content || 'Max rounds reached. Partial results returned.';\n }\n }\n\n const durationMs = Date.now() - startTime;\n logger.info(COMPONENT, `${agentName} completed in ${durationMs}ms (${rounds} rounds, ${toolsUsed.length} tool calls)`);\n\n // Output validation: check for empty, too-short, or error-like responses\n const validated = validateSubAgentOutput(finalContent);\n if (!validated) {\n logger.warn(COMPONENT, `[${agentName}] Output failed validation: \"${finalContent.slice(0, 80)}...\"`);\n }\n\n // Phase 8: log agent completion\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: !finalContent.toLowerCase().startsWith('error') && validated });\n } catch { /* non-critical */ }\n\n return {\n content: finalContent,\n toolsUsed: [...new Set(toolsUsed)],\n success: !finalContent.toLowerCase().startsWith('error') && validated,\n durationMs,\n rounds,\n validated,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n logger.error(COMPONENT, `${agentName} failed: ${(err as Error).message}`);\n // Phase 8: log agent error\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'agent_complete', agent: agentName, task: config.task.slice(0, 200), rounds, success: false, error: (err as Error).message });\n } catch { /* non-critical */ }\n return {\n content: `Sub-agent error: ${(err as Error).message}`,\n toolsUsed: [...new Set(toolsUsed)],\n success: false,\n durationMs,\n rounds,\n validated: false,\n };\n } finally {\n activeSubAgentIds.delete(agentTrackingId);\n\n // ── Message Bus: unregister mailbox on completion ──\n unregisterMailbox(agentName);\n\n // ── Agent Pool: release back to pool for future reuse ──\n if (config.reusePool && pooledAgent) {\n releaseAgent(pooledAgent.id, messages, toolsUsed, rounds);\n }\n }\n}\n\n/** Validate sub-agent output for quality */\nfunction validateSubAgentOutput(content: string): boolean {\n if (!content || content.trim().length < 20) return false;\n const lower = content.toLowerCase();\n if (lower.startsWith('i cannot') || lower.startsWith('i\\'m unable') || lower.startsWith('i am unable')) return false;\n if (lower.startsWith('error:') || lower.startsWith('sub-agent error:')) return false;\n if (lower === 'task completed.' && content.length < 20) return false;\n return true;\n}\n\n/** Get count of currently active sub-agents */\nexport function getActiveSubAgentCount(): number {\n return activeSubAgentIds.size;\n}\n"],"mappings":"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","names":[]}
|
package/dist/agent/toolRunner.js
CHANGED
|
@@ -473,6 +473,23 @@ Rewrite the path to live inside the self-mod target, OR retag the goal to remove
|
|
|
473
473
|
})();
|
|
474
474
|
}
|
|
475
475
|
(async () => {
|
|
476
|
+
try {
|
|
477
|
+
const { logActivity } = await import("../telemetry/activityLog.js");
|
|
478
|
+
logActivity({ event: "tool_call", tool: handler.name, channel: channel ?? "unknown" });
|
|
479
|
+
if (MUTATING_TOOLS.has(handler.name)) {
|
|
480
|
+
logActivity({ event: "file_edit", tool: handler.name, path: args.path || args.file_path || args.filePath });
|
|
481
|
+
}
|
|
482
|
+
if (handler.name === "web_search") {
|
|
483
|
+
logActivity({ event: "web_search", query: args.query || args.q });
|
|
484
|
+
}
|
|
485
|
+
if (handler.name === "web_fetch") {
|
|
486
|
+
logActivity({ event: "web_fetch", url: args.url || args.target });
|
|
487
|
+
}
|
|
488
|
+
if (handler.name === "run_eval" || handler.name === "eval_suite") {
|
|
489
|
+
logActivity({ event: "eval_run", suite: args.suite || args.name });
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
476
493
|
const cfg = loadConfig();
|
|
477
494
|
if (cfg.telemetry?.enabled) {
|
|
478
495
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/toolRunner.ts"],"sourcesContent":["/**\n * TITAN — Tool Runner\n * Executes tool calls from the LLM with sandboxing, timeouts, and result formatting.\n */\nimport type { ToolCall, ToolDefinition } from '../providers/base.js';\nimport { appendFileSync, readFileSync, existsSync } from 'fs';\nimport { TELEMETRY_EVENTS_PATH } from '../utils/constants.js';\nimport { executeToolsParallel } from './parallelTools.js';\nimport { runPreTool, runPostTool } from '../plugins/contextEngine.js';\nimport type { ContextEnginePlugin } from '../plugins/contextEngine.js';\n\n/** Tool hook plugins — set during agent initialization */\nlet toolHookPlugins: ContextEnginePlugin[] = [];\nexport function setToolHookPlugins(plugins: ContextEnginePlugin[]): void {\n toolHookPlugins = plugins;\n}\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\nimport { checkAutonomy } from './autonomy.js';\nimport { isToolSkillEnabled } from '../skills/registry.js';\nimport { redactSecrets } from '../security/secretGuard.js';\nimport { scanAndRedactPII, fullExfilScan } from '../security/exfilScan.js';\nimport { scanCommand, scanURL } from '../security/preExecScan.js';\nimport { runPreToolShellHooks, runPostToolShellHooks } from '../hooks/shellHooks.js';\nimport { createCheckpoint } from '../checkpoint/manager.js';\n\n/** Compute a lightweight unified diff between old and new file content */\nfunction computeUnifiedDiff(filePath: string, oldContent: string, newContent: string): string {\n if (oldContent === newContent) return `// No changes to ${filePath}`;\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n const header = `--- ${filePath}\\n+++ ${filePath}`;\n const hunks: string[] = [];\n let i = 0, j = 0;\n while (i < oldLines.length || j < newLines.length) {\n if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {\n i++; j++; continue;\n }\n const startI = i, startJ = j;\n const removed: string[] = [];\n const added: string[] = [];\n while (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {\n removed.push(oldLines[i++]);\n }\n while (j < newLines.length && (i >= oldLines.length || oldLines[i] !== newLines[j])) {\n added.push(newLines[j++]);\n }\n if (removed.length || added.length) {\n const ctxBefore = oldLines.slice(Math.max(0, startI - 2), startI);\n const ctxAfter = oldLines.slice(i, Math.min(oldLines.length, i + 2));\n hunks.push([\n ...ctxBefore.map(l => ` ${l}`),\n ...removed.map(l => `-${l}`),\n ...added.map(l => `+${l}`),\n ...ctxAfter.map(l => ` ${l}`),\n ].join('\\n'));\n }\n }\n const body = hunks.join('\\n---\\n');\n return `${header}\\n${body}`;\n}\nimport { getCachedToolResult, cacheToolResult } from './trajectoryCompressor.js';\nimport { classifyProviderError, FailoverReason } from '../providers/errorTaxonomy.js';\nimport { snapshotBeforeWrite } from './shadowGit.js';\nimport { captureWrite, shouldCapture } from './selfProposals.js';\nimport { getSessionGoal } from './autonomyContext.js';\n\nconst COMPONENT = 'ToolRunner';\n\n/**\n * G1: Sanitize base64 image data from tool results (OpenClaw pattern).\n * Prevents token explosion when vision/screenshot tools return raw base64.\n * Replaces data URIs with a compact placeholder showing byte count.\n */\nfunction sanitizeBase64(content: string): string {\n return content.replace(\n /data:image\\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g,\n (match) => {\n const bytes = Math.ceil((match.length - match.indexOf(',') - 1) * 0.75);\n return `[image: ${(bytes / 1024).toFixed(1)}KB omitted]`;\n },\n );\n}\n\n/** Error classification for retry decisions */\nexport type ErrorClass = 'transient' | 'permanent' | 'timeout' | 'rate_limit';\n\n/** Classify an error to determine if retry is appropriate.\n * Delegates to the centralized error taxonomy, then maps back to ErrorClass\n * for backward compatibility with tool execution retry logic.\n */\nexport function classifyError(error: Error, _toolName: string): ErrorClass {\n const classified = classifyProviderError(error);\n switch (classified.reason) {\n case FailoverReason.TIMEOUT:\n return 'timeout';\n case FailoverReason.RATE_LIMIT:\n return 'rate_limit';\n case FailoverReason.SERVER_ERROR:\n case FailoverReason.NETWORK_ERROR:\n case FailoverReason.OVERLOADED:\n case FailoverReason.EMPTY_RESPONSE:\n return 'transient';\n default:\n return classified.retryable ? 'transient' : 'permanent';\n }\n}\n\n/** Tool execution result */\nexport interface ToolResult {\n toolCallId: string;\n name: string;\n content: string;\n success: boolean;\n durationMs: number;\n /** Number of retry attempts made (0 = first try succeeded/failed) */\n retryCount?: number;\n /** Error classification if the tool failed */\n errorClass?: ErrorClass;\n /** Inline unified diff for file-modifying tools (write_file, edit_file, apply_patch) */\n diff?: string;\n}\n\n/** A registered tool handler */\nexport interface ToolHandler {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (args: Record<string, unknown>) => Promise<string>;\n}\n\n/** Global tool registry */\nconst toolRegistry: Map<string, ToolHandler> = new Map();\n\n/** Register a tool */\nexport function registerTool(handler: ToolHandler): void {\n toolRegistry.set(handler.name, handler);\n logger.debug(COMPONENT, `Registered tool: ${handler.name}`);\n}\n\n/** Unregister a tool */\nexport function unregisterTool(name: string): void {\n toolRegistry.delete(name);\n}\n\n/** Get all registered tools */\nexport function getRegisteredTools(): ToolHandler[] {\n return Array.from(toolRegistry.values());\n}\n\n/** Convert registered tools to LLM tool definitions */\nexport function getToolDefinitions(): ToolDefinition[] {\n const config = loadConfig();\n const allowed = new Set(config.security.allowedTools);\n const denied = new Set(config.security.deniedTools);\n\n return Array.from(toolRegistry.values())\n .filter((tool) => {\n if (denied.has(tool.name)) return false;\n if (allowed.size > 0 && !allowed.has(tool.name)) return false;\n if (!isToolSkillEnabled(tool.name)) return false;\n return true;\n })\n .map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\n/** Execute a single tool call */\nexport async function executeTool(toolCall: ToolCall, channel?: string): Promise<ToolResult> {\n const config = loadConfig();\n const startTime = Date.now();\n const handler = toolRegistry.get(toolCall.function.name);\n\n if (!handler) {\n // LangGraph pattern: tell the LLM which tools actually exist so it can self-correct\n const available = Array.from(toolRegistry.keys()).sort();\n const suggestions = available.filter(t => {\n const name = toolCall.function.name.toLowerCase();\n return t.toLowerCase().includes(name.slice(0, 4)) || name.includes(t.slice(0, 4));\n }).slice(0, 5);\n const hint = suggestions.length > 0\n ? `\\nDid you mean: ${suggestions.join(', ')}?`\n : `\\nAvailable tools include: ${available.slice(0, 20).join(', ')}${available.length > 20 ? ` (and ${available.length - 20} more)` : ''}`;\n return {\n toolCallId: toolCall.id,\n name: toolCall.function.name,\n content: `Error: \"${toolCall.function.name}\" is not a valid tool.${hint}\\nPlease use one of the available tools.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check permissions\n if (config.security.deniedTools.includes(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is denied by security policy`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check if parent skill is enabled\n if (!isToolSkillEnabled(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is disabled — its parent skill is turned off. Enable it in Mission Control.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Parse arguments\n let args: Record<string, unknown> = {};\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (parseErr) {\n logger.warn('ToolRunner', `Malformed JSON args for ${handler.name}: ${(parseErr as Error).message} — raw: ${(toolCall.function.arguments || '').slice(0, 200)}`);\n // Try to salvage: if it looks like a truncated JSON, extract what we can\n const salvageMatch = (toolCall.function.arguments || '').match(/\\{[\\s\\S]*/);\n if (salvageMatch) {\n try {\n // Attempt to close the JSON and parse\n const fixed = salvageMatch[0].replace(/,?\\s*$/, '}');\n args = JSON.parse(fixed);\n logger.info('ToolRunner', `Salvaged partial JSON args for ${handler.name}`);\n } catch {\n // A5: Return error instead of executing with empty args (LangGraph pattern)\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Could not parse arguments for \"${handler.name}\". Raw: ${(toolCall.function.arguments || '').slice(0, 200)}. Please provide valid JSON arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Schema validation: check required parameters before execution (LangGraph pattern)\n if (handler.parameters && typeof handler.parameters === 'object') {\n const schema = handler.parameters as { required?: string[]; properties?: Record<string, unknown> };\n if (schema.required && Array.isArray(schema.required)) {\n const missing = schema.required.filter(key => args[key] === undefined || args[key] === null);\n if (missing.length > 0) {\n const available = schema.properties ? Object.keys(schema.properties) : [];\n logger.warn('ToolRunner', `[SchemaValidation] ${handler.name}: missing required params: ${missing.join(', ')}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Missing required parameter(s): ${missing.join(', ')}. ` +\n `Expected parameters: ${available.join(', ')}. Please provide all required arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Guardrails: validate tool call before execution\n try {\n const { guardToolCall } = await import('./guardrails.js');\n const guardResult = guardToolCall(handler.name, args);\n if (!guardResult.allowed) {\n logger.warn('ToolRunner', `[Guardrails] Blocked ${handler.name}: ${guardResult.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool call blocked by guardrails — ${guardResult.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* guardrails unavailable — continue */ }\n\n // Read-only tool result cache (60s TTL, helper self-gates to read-only allowlist)\n const cacheArgKey = toolCall.function.arguments || '{}';\n const cachedResult = getCachedToolResult(handler.name, cacheArgKey);\n if (cachedResult !== null) {\n logger.info(COMPONENT, `[Cache HIT] ${handler.name}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: cachedResult,\n success: true,\n durationMs: Date.now() - startTime,\n };\n }\n\n logger.info(COMPONENT, `Executing tool: ${handler.name}`);\n\n // v5.0: Pre-execution scanner for dangerous commands\n if (handler.name === 'shell' || handler.name === 'code_exec') {\n const cmdArg = (args.command || args.code || args.script || '') as string;\n const scan = scanCommand(cmdArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this command\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (scan.warnings.length > 0 && scan.level === 'warn') {\n logger.warn('ToolRunner', `Pre-exec warnings for ${handler.name}: ${scan.warnings.join('; ')}`);\n }\n }\n if (handler.name === 'browser_navigate' || handler.name === 'browser_auto_nav') {\n const urlArg = (args.url || args.target || '') as string;\n const scan = scanURL(urlArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this URL\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n\n // v5.0: Shell hooks — pre-tool\n const { getCurrentSessionId } = await import('./agent.js');\n const sessionId = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const shellPre = await runPreToolShellHooks(handler.name, args, sessionId || toolCall.id, 'default', 0);\n if (!shellPre.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by shell hook: ' + (shellPre.reason || 'Hook denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (shellPre.modifiedArgs) args = shellPre.modifiedArgs;\n\n // Pre-tool hooks — plugins can block or modify args\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPreTool(toolHookPlugins, handler.name, args);\n if (!hookResult.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by hook: ' + (hookResult.reason || 'Plugin denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (hookResult.modifiedArgs) args = hookResult.modifiedArgs;\n }\n\n // Autonomy gate: check if the tool is permitted under current mode\n const autonomyResult = await checkAutonomy(handler.name, args, channel);\n if (!autonomyResult.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Action blocked by autonomy policy: ' + (autonomyResult.reason || 'Not permitted'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Shadow git checkpoint — snapshot files before mutation (fire-and-forget)\n const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'append_file', 'apply_patch']);\n\n // v4.9.0-local.7: kill-switch gate for file mutations. If the kill switch\n // is engaged, refuse write/edit/append/apply_patch so the initiative loop\n // can't keep accumulating fix-oscillations while the human hasn't resumed.\n // This closes the gap where `spawn_agent`, `autopilot`, and the pressure\n // cycle were gated but the main agent's tool path was not — meaning\n // initiative could keep rewriting the same files for hours after a kill.\n // See kill-switch.json `history` for the trigger; resume via\n // POST /api/safety/resume.\n if (MUTATING_TOOLS.has(handler.name)) {\n try {\n const { isKilled, getState } = await import('../safety/killSwitch.js');\n if (isKilled()) {\n const state = getState();\n const lastReason = state?.lastEvent?.reason ?? 'kill switch engaged';\n logger.warn(COMPONENT, `[KillSwitch] Refusing ${handler.name} — ${lastReason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: File mutation refused — kill switch is engaged (${lastReason}). ` +\n `Resume via POST /api/safety/resume after investigating the trigger, ` +\n `then retry. Do NOT retry this tool call until resumed.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* kill switch module unavailable — fall through (fail-open on infra error) */ }\n }\n\n // v4.9.0-local.8: Self-mod scope lock + staging.\n //\n // Three-layer policy when the active session has a goal tagged as\n // self-modifying (see config.autonomy.selfMod.tags):\n // 1. Writes to paths OUTSIDE config.autonomy.selfMod.target are refused\n // (prevents LARPing self-improvement by writing to ~/titan-saas etc)\n // 2. When staging is enabled, writes INSIDE target are diverted to a\n // per-goal staging directory and a `self_mod_pr` approval is filed\n // 3. The original path is stored as `targetPath` on the staging entry\n // so the human sees what would land where if they approve the PR\n //\n // This is the deeper fix for the pattern observed 2026-04-18 where a\n // \"self-healing framework\" goal completed 100% by writing to an unrelated\n // Next.js app.\n let stagedRedirect: { stagedPath: string; targetPath: string } | null = null;\n if (MUTATING_TOOLS.has(handler.name)) {\n const rawFilePath = (args.path || args.file_path || args.filePath) as string | undefined;\n if (rawFilePath) {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { decideScope } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const decision = decideScope(sid, rawFilePath);\n if (decision.action === 'reject') {\n logger.warn(COMPONENT, `[ScopeLock] Refusing ${handler.name}: ${decision.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${decision.reason}\\n\\nRewrite the path to live inside the self-mod target, OR retag the goal to remove self-mod tags, OR pause the goal and create a properly-scoped one.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (decision.action === 'stage' && decision.stagedPath && decision.targetPath) {\n stagedRedirect = { stagedPath: decision.stagedPath, targetPath: decision.targetPath };\n // Rewrite the tool args so the handler writes to staging.\n // Preserve both `path` and `file_path` variants since\n // different tools use different field names.\n if (args.path !== undefined) args.path = decision.stagedPath;\n if (args.file_path !== undefined) args.file_path = decision.stagedPath;\n if (args.filePath !== undefined) args.filePath = decision.stagedPath;\n // Ensure the staged parent dir exists; write_file may not\n // mkdir -p on all code paths.\n try {\n const { mkdirSync } = await import('fs');\n const { dirname } = await import('path');\n mkdirSync(dirname(decision.stagedPath), { recursive: true });\n } catch { /* best-effort */ }\n logger.info(COMPONENT, `[SelfModStaging] Diverting ${handler.name} → ${decision.stagedPath} (would land at ${decision.targetPath} on approval)`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `[ScopeLock] check failed (fail-open): ${(err as Error).message}`);\n }\n }\n }\n\n // v5.0: Filesystem checkpoint before destructive operations\n if (MUTATING_TOOLS.has(handler.name)) {\n const cpPaths: string[] = [];\n const cpPath = (args.path || args.file_path || args.filePath) as string;\n if (cpPath) cpPaths.push(cpPath);\n if (cpPaths.length > 0) {\n createCheckpoint(sessionId || toolCall.id, handler.name, args, cpPaths);\n }\n }\n\n if (MUTATING_TOOLS.has(handler.name)) {\n // Use the (potentially rewritten) path for shadow-git + fix-oscillation\n const filePath = (args.path || args.file_path || args.filePath) as string;\n if (filePath) {\n snapshotBeforeWrite(handler.name, filePath).catch(err =>\n logger.debug(COMPONENT, `Shadow checkpoint skipped: ${(err as Error).message}`),\n );\n // v4.9.0: fix-oscillation tracker. Same file written/edited\n // twice within 24h flags as oscillation, which feeds the\n // kill switch (3+ oscillations → kill). Best-effort — never\n // blocks the write.\n (async () => {\n try {\n const { recordFixEvent } = await import('../safety/fixOscillation.js');\n recordFixEvent({\n target: filePath,\n kind: 'file',\n detail: `${handler.name} via ${channel ?? 'unknown'}`,\n });\n } catch (err) {\n logger.debug(COMPONENT, `Fix-oscillation skipped: ${(err as Error).message}`);\n }\n })();\n }\n // v4.8.0: self-proposal capture — if this write is happening inside\n // an autonomous Soma-driven session, stash a copy for specialist\n // review. Fire-and-forget — never blocks tool execution.\n captureSelfProposalIfApplicable(handler.name, args).catch(err =>\n logger.debug(COMPONENT, `Self-proposal capture skipped: ${(err as Error).message}`),\n );\n }\n\n // Per-tool timeout lookup\n const toolTimeouts = (config.security as Record<string, unknown>).toolTimeouts as Record<string, number> | undefined;\n const baseTimeout = toolTimeouts?.[handler.name] || config.security.commandTimeout || 30000;\n\n // Retry config\n const retryConfig = (config.security as Record<string, unknown>).toolRetry as { enabled?: boolean; maxRetries?: number; backoffBaseMs?: number } | undefined;\n const retryEnabled = retryConfig?.enabled !== false;\n const maxRetries = retryConfig?.maxRetries ?? 3;\n const backoffBase = retryConfig?.backoffBaseMs ?? 1000;\n\n let lastError: Error | null = null;\n let lastErrorClass: ErrorClass = 'permanent';\n let attempt = 0;\n\n // Capture pre-execution file state for diff generation\n let preContent: string | undefined;\n let filePathForDiff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name)) {\n const pathArg = args.path as string | undefined;\n if (pathArg) {\n filePathForDiff = pathArg;\n try {\n if (existsSync(pathArg)) preContent = readFileSync(pathArg, 'utf-8');\n } catch { /* ignore read errors */ }\n }\n }\n\n // Swarm invariants — hard safety rules (fail-open)\n try {\n const { checkInvariants } = await import('../safety/invariants.js');\n const invariant = checkInvariants(handler.name, args);\n if (!invariant.pass) {\n logger.warn(COMPONENT, `[Invariant] Blocked ${handler.name}: ${invariant.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `INVARIANT_VIOLATION: ${invariant.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Invariant check failed (fail-open): ${(err as Error).message}`);\n }\n\n for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {\n try {\n // On timeout retry, double the timeout\n const timeout = (attempt > 0 && lastErrorClass === 'timeout') ? baseTimeout * 2 : baseTimeout;\n\n let result = await Promise.race([\n handler.execute(args),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(`Tool \"${handler.name}\" timed out after ${timeout}ms`)), timeout)\n ),\n ]);\n\n // Secret exfiltration guard — scan tool output before it leaves\n result = redactSecrets(result);\n\n // v5.0: PII redaction (privacy compliance)\n const config = loadConfig();\n if (config.security?.redactPII) {\n result = scanAndRedactPII(result);\n }\n\n // v5.0: Full exfiltration scan (layer 2-5) when configured\n if (config.security?.secretScan?.level === 'full') {\n const scan = fullExfilScan(result, 'tool_output');\n if (scan.blocked) {\n logger.warn('ToolRunner', `Exfiltration scan blocked ${handler.name}: ${scan.findings.map(f => f.type).join(', ')}`);\n }\n result = scan.redacted;\n }\n\n const durationMs = Date.now() - startTime;\n if (attempt > 0) {\n logger.info(COMPONENT, `Tool ${handler.name} succeeded on retry ${attempt} in ${durationMs}ms`);\n } else {\n logger.info(COMPONENT, `Tool ${handler.name} completed in ${durationMs}ms`);\n }\n\n // G1: Strip base64 image data before size check (prevents token explosion)\n let finalContent = sanitizeBase64(result);\n\n // Smart truncation — keep head + tail for large results (TITAN pattern)\n if (finalContent.length > 30000) {\n const head = finalContent.slice(0, 20000);\n const tail = finalContent.slice(-5000);\n finalContent = head + '\\n\\n[... ' + (finalContent.length - 25000) + ' chars omitted ...]\\n\\n' + tail;\n logger.info(COMPONENT, `Tool ${handler.name} output truncated: ${result.length} → ${finalContent.length} chars`);\n }\n\n // v5.0: Shell hooks — post-tool\n const shellPost = await runPostToolShellHooks(handler.name, args, finalContent, sessionId || toolCall.id, 'default', 0);\n if (shellPost !== undefined) finalContent = shellPost;\n\n // Post-tool hooks — plugins can modify result\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPostTool(toolHookPlugins, handler.name, args, { content: finalContent, success: true, durationMs });\n if (hookResult.modifiedContent !== undefined) finalContent = hookResult.modifiedContent;\n }\n\n // Cache the result for read-only tools (helper self-gates)\n cacheToolResult(handler.name, cacheArgKey, finalContent);\n\n // v4.9.0-local.8: if this write was diverted to staging, record\n // it in the self_mod_pr bundle. Fire-and-forget — must NOT block\n // the tool's return value or the agent loop.\n if (stagedRedirect) {\n (async () => {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { recordStagedWrite } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n await recordStagedWrite({\n sessionId: sid,\n toolName: handler.name,\n stagedPath: stagedRedirect!.stagedPath,\n targetPath: stagedRedirect!.targetPath,\n });\n } catch (err) {\n logger.debug(COMPONENT, `[SelfModStaging] recordStagedWrite failed: ${(err as Error).message}`);\n }\n })();\n }\n\n // Fire-and-forget telemetry\n (async () => {\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: true, durationMs, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, true, durationMs, undefined, sid ?? undefined);\n })();\n\n // Compute inline diff for file-modifying tools\n let diff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name) && filePathForDiff && !stagedRedirect) {\n try {\n const postContent = existsSync(filePathForDiff) ? readFileSync(filePathForDiff, 'utf-8') : '';\n diff = computeUnifiedDiff(filePathForDiff, preContent ?? '', postContent);\n } catch { /* ignore diff errors */ }\n }\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: stagedRedirect\n ? `${finalContent}\\n\\n[SelfModStaging] Diverted to staging: ${stagedRedirect.stagedPath}. A human approval is pending before this lands at ${stagedRedirect.targetPath}.`\n : finalContent,\n success: true,\n durationMs,\n retryCount: attempt,\n diff,\n };\n } catch (error) {\n lastError = error as Error;\n lastErrorClass = classifyError(lastError, handler.name);\n\n // Don't retry permanent errors\n if (lastErrorClass === 'permanent') {\n break;\n }\n\n // Don't retry if this was the last attempt\n if (attempt >= maxRetries || !retryEnabled) {\n break;\n }\n\n // Exponential backoff: 1s, 2s, 4s (capped at 8s)\n const delay = Math.min(backoffBase * Math.pow(2, attempt), 8000);\n logger.warn(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}, attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message} — retrying in ${delay}ms`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n // All retries exhausted or permanent error\n const durationMs = Date.now() - startTime;\n const errorMsg = lastError?.message || 'Unknown error';\n const retryCount = attempt; // actual number of retries performed (matches success path)\n logger.error(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}${retryCount > 0 ? `, ${retryCount} retries` : ''}): ${errorMsg}`);\n\n // Fire-and-forget telemetry\n (async () => {\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: false, durationMs, errorClass: lastErrorClass, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, false, durationMs, lastErrorClass, sid ?? undefined);\n })();\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${errorMsg}`,\n success: false,\n durationMs,\n retryCount,\n errorClass: lastErrorClass,\n };\n}\n\n/** Execute multiple tool calls (in parallel where possible, with write-conflict detection) */\nexport async function executeTools(toolCalls: ToolCall[], channel?: string): Promise<ToolResult[]> {\n // Single tool — fast path\n if (toolCalls.length <= 1) {\n return Promise.all(toolCalls.map(tc => executeTool(tc, channel)));\n }\n\n // Multiple tools — use parallelTools engine with write-conflict detection\n const parallelCalls = toolCalls.map(tc => {\n let args: Record<string, unknown> = {};\n try { args = JSON.parse(tc.function.arguments); } catch { /* use empty */ }\n return { id: tc.id, name: tc.function.name, args };\n });\n\n const executor = async (name: string, args: Record<string, unknown>): Promise<string> => {\n // Build a synthetic ToolCall for executeTool\n const syntheticTc: ToolCall = {\n id: '',\n type: 'function',\n function: { name, arguments: JSON.stringify(args) },\n };\n const result = await executeTool(syntheticTc, channel);\n return result.content;\n };\n\n const parallelResults = await executeToolsParallel(parallelCalls, executor);\n\n // Map back to ToolResult format with full metadata\n return parallelResults.map(pr => ({\n toolCallId: pr.toolCallId,\n name: pr.name,\n content: pr.content,\n success: !pr.content.startsWith('Error:'),\n durationMs: 0,\n }));\n}\n\n// ── Self-proposal capture helper (v4.8.0) ────────────────────────────────\n\n/**\n * If the current write is happening in an autonomous, Soma-driven session,\n * stash a copy of the written content for specialist review. Silent no-op\n * in all other cases (user-driven edits, non-autonomous mode, or when\n * selfMod.enabled is false in config).\n */\nasync function captureSelfProposalIfApplicable(\n toolName: string,\n args: Record<string, unknown>,\n): Promise<void> {\n // Resolve what we can from the current autonomous context\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sessionId: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const sessionGoal = getSessionGoal(sessionId);\n const config = loadConfig();\n const autonomous = (config.autonomy?.mode === 'autonomous');\n const goalProposedBy = sessionGoal?.proposedBy ?? null;\n\n if (!shouldCapture({ toolName, autonomous, goalProposedBy })) return;\n\n const filePath = (args.path || args.file_path || args.filePath) as string | undefined;\n const content = (args.content || args.new_text || args.data) as string | undefined;\n if (!filePath || !content) return;\n\n captureWrite({\n toolName,\n filePath,\n content,\n sessionId,\n agentId: null, // filled by downstream if needed\n goalId: sessionGoal?.goalId ?? null,\n goalTitle: sessionGoal?.goalTitle ?? null,\n goalProposedBy,\n });\n}\n"],"mappings":"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","names":["getCurrentSessionId","config","durationMs"]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/toolRunner.ts"],"sourcesContent":["/**\n * TITAN — Tool Runner\n * Executes tool calls from the LLM with sandboxing, timeouts, and result formatting.\n */\nimport type { ToolCall, ToolDefinition } from '../providers/base.js';\nimport { appendFileSync, readFileSync, existsSync } from 'fs';\nimport { TELEMETRY_EVENTS_PATH } from '../utils/constants.js';\nimport { executeToolsParallel } from './parallelTools.js';\nimport { runPreTool, runPostTool } from '../plugins/contextEngine.js';\nimport type { ContextEnginePlugin } from '../plugins/contextEngine.js';\n\n/** Tool hook plugins — set during agent initialization */\nlet toolHookPlugins: ContextEnginePlugin[] = [];\nexport function setToolHookPlugins(plugins: ContextEnginePlugin[]): void {\n toolHookPlugins = plugins;\n}\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\nimport { checkAutonomy } from './autonomy.js';\nimport { isToolSkillEnabled } from '../skills/registry.js';\nimport { redactSecrets } from '../security/secretGuard.js';\nimport { scanAndRedactPII, fullExfilScan } from '../security/exfilScan.js';\nimport { scanCommand, scanURL } from '../security/preExecScan.js';\nimport { runPreToolShellHooks, runPostToolShellHooks } from '../hooks/shellHooks.js';\nimport { createCheckpoint } from '../checkpoint/manager.js';\n\n/** Compute a lightweight unified diff between old and new file content */\nfunction computeUnifiedDiff(filePath: string, oldContent: string, newContent: string): string {\n if (oldContent === newContent) return `// No changes to ${filePath}`;\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n const header = `--- ${filePath}\\n+++ ${filePath}`;\n const hunks: string[] = [];\n let i = 0, j = 0;\n while (i < oldLines.length || j < newLines.length) {\n if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {\n i++; j++; continue;\n }\n const startI = i, startJ = j;\n const removed: string[] = [];\n const added: string[] = [];\n while (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {\n removed.push(oldLines[i++]);\n }\n while (j < newLines.length && (i >= oldLines.length || oldLines[i] !== newLines[j])) {\n added.push(newLines[j++]);\n }\n if (removed.length || added.length) {\n const ctxBefore = oldLines.slice(Math.max(0, startI - 2), startI);\n const ctxAfter = oldLines.slice(i, Math.min(oldLines.length, i + 2));\n hunks.push([\n ...ctxBefore.map(l => ` ${l}`),\n ...removed.map(l => `-${l}`),\n ...added.map(l => `+${l}`),\n ...ctxAfter.map(l => ` ${l}`),\n ].join('\\n'));\n }\n }\n const body = hunks.join('\\n---\\n');\n return `${header}\\n${body}`;\n}\nimport { getCachedToolResult, cacheToolResult } from './trajectoryCompressor.js';\nimport { classifyProviderError, FailoverReason } from '../providers/errorTaxonomy.js';\nimport { snapshotBeforeWrite } from './shadowGit.js';\nimport { captureWrite, shouldCapture } from './selfProposals.js';\nimport { getSessionGoal } from './autonomyContext.js';\n\nconst COMPONENT = 'ToolRunner';\n\n/**\n * G1: Sanitize base64 image data from tool results (OpenClaw pattern).\n * Prevents token explosion when vision/screenshot tools return raw base64.\n * Replaces data URIs with a compact placeholder showing byte count.\n */\nfunction sanitizeBase64(content: string): string {\n return content.replace(\n /data:image\\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g,\n (match) => {\n const bytes = Math.ceil((match.length - match.indexOf(',') - 1) * 0.75);\n return `[image: ${(bytes / 1024).toFixed(1)}KB omitted]`;\n },\n );\n}\n\n/** Error classification for retry decisions */\nexport type ErrorClass = 'transient' | 'permanent' | 'timeout' | 'rate_limit';\n\n/** Classify an error to determine if retry is appropriate.\n * Delegates to the centralized error taxonomy, then maps back to ErrorClass\n * for backward compatibility with tool execution retry logic.\n */\nexport function classifyError(error: Error, _toolName: string): ErrorClass {\n const classified = classifyProviderError(error);\n switch (classified.reason) {\n case FailoverReason.TIMEOUT:\n return 'timeout';\n case FailoverReason.RATE_LIMIT:\n return 'rate_limit';\n case FailoverReason.SERVER_ERROR:\n case FailoverReason.NETWORK_ERROR:\n case FailoverReason.OVERLOADED:\n case FailoverReason.EMPTY_RESPONSE:\n return 'transient';\n default:\n return classified.retryable ? 'transient' : 'permanent';\n }\n}\n\n/** Tool execution result */\nexport interface ToolResult {\n toolCallId: string;\n name: string;\n content: string;\n success: boolean;\n durationMs: number;\n /** Number of retry attempts made (0 = first try succeeded/failed) */\n retryCount?: number;\n /** Error classification if the tool failed */\n errorClass?: ErrorClass;\n /** Inline unified diff for file-modifying tools (write_file, edit_file, apply_patch) */\n diff?: string;\n}\n\n/** A registered tool handler */\nexport interface ToolHandler {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (args: Record<string, unknown>) => Promise<string>;\n}\n\n/** Global tool registry */\nconst toolRegistry: Map<string, ToolHandler> = new Map();\n\n/** Register a tool */\nexport function registerTool(handler: ToolHandler): void {\n toolRegistry.set(handler.name, handler);\n logger.debug(COMPONENT, `Registered tool: ${handler.name}`);\n}\n\n/** Unregister a tool */\nexport function unregisterTool(name: string): void {\n toolRegistry.delete(name);\n}\n\n/** Get all registered tools */\nexport function getRegisteredTools(): ToolHandler[] {\n return Array.from(toolRegistry.values());\n}\n\n/** Convert registered tools to LLM tool definitions */\nexport function getToolDefinitions(): ToolDefinition[] {\n const config = loadConfig();\n const allowed = new Set(config.security.allowedTools);\n const denied = new Set(config.security.deniedTools);\n\n return Array.from(toolRegistry.values())\n .filter((tool) => {\n if (denied.has(tool.name)) return false;\n if (allowed.size > 0 && !allowed.has(tool.name)) return false;\n if (!isToolSkillEnabled(tool.name)) return false;\n return true;\n })\n .map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\n/** Execute a single tool call */\nexport async function executeTool(toolCall: ToolCall, channel?: string): Promise<ToolResult> {\n const config = loadConfig();\n const startTime = Date.now();\n const handler = toolRegistry.get(toolCall.function.name);\n\n if (!handler) {\n // LangGraph pattern: tell the LLM which tools actually exist so it can self-correct\n const available = Array.from(toolRegistry.keys()).sort();\n const suggestions = available.filter(t => {\n const name = toolCall.function.name.toLowerCase();\n return t.toLowerCase().includes(name.slice(0, 4)) || name.includes(t.slice(0, 4));\n }).slice(0, 5);\n const hint = suggestions.length > 0\n ? `\\nDid you mean: ${suggestions.join(', ')}?`\n : `\\nAvailable tools include: ${available.slice(0, 20).join(', ')}${available.length > 20 ? ` (and ${available.length - 20} more)` : ''}`;\n return {\n toolCallId: toolCall.id,\n name: toolCall.function.name,\n content: `Error: \"${toolCall.function.name}\" is not a valid tool.${hint}\\nPlease use one of the available tools.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check permissions\n if (config.security.deniedTools.includes(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is denied by security policy`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check if parent skill is enabled\n if (!isToolSkillEnabled(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is disabled — its parent skill is turned off. Enable it in Mission Control.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Parse arguments\n let args: Record<string, unknown> = {};\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (parseErr) {\n logger.warn('ToolRunner', `Malformed JSON args for ${handler.name}: ${(parseErr as Error).message} — raw: ${(toolCall.function.arguments || '').slice(0, 200)}`);\n // Try to salvage: if it looks like a truncated JSON, extract what we can\n const salvageMatch = (toolCall.function.arguments || '').match(/\\{[\\s\\S]*/);\n if (salvageMatch) {\n try {\n // Attempt to close the JSON and parse\n const fixed = salvageMatch[0].replace(/,?\\s*$/, '}');\n args = JSON.parse(fixed);\n logger.info('ToolRunner', `Salvaged partial JSON args for ${handler.name}`);\n } catch {\n // A5: Return error instead of executing with empty args (LangGraph pattern)\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Could not parse arguments for \"${handler.name}\". Raw: ${(toolCall.function.arguments || '').slice(0, 200)}. Please provide valid JSON arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Schema validation: check required parameters before execution (LangGraph pattern)\n if (handler.parameters && typeof handler.parameters === 'object') {\n const schema = handler.parameters as { required?: string[]; properties?: Record<string, unknown> };\n if (schema.required && Array.isArray(schema.required)) {\n const missing = schema.required.filter(key => args[key] === undefined || args[key] === null);\n if (missing.length > 0) {\n const available = schema.properties ? Object.keys(schema.properties) : [];\n logger.warn('ToolRunner', `[SchemaValidation] ${handler.name}: missing required params: ${missing.join(', ')}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Missing required parameter(s): ${missing.join(', ')}. ` +\n `Expected parameters: ${available.join(', ')}. Please provide all required arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Guardrails: validate tool call before execution\n try {\n const { guardToolCall } = await import('./guardrails.js');\n const guardResult = guardToolCall(handler.name, args);\n if (!guardResult.allowed) {\n logger.warn('ToolRunner', `[Guardrails] Blocked ${handler.name}: ${guardResult.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool call blocked by guardrails — ${guardResult.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* guardrails unavailable — continue */ }\n\n // Read-only tool result cache (60s TTL, helper self-gates to read-only allowlist)\n const cacheArgKey = toolCall.function.arguments || '{}';\n const cachedResult = getCachedToolResult(handler.name, cacheArgKey);\n if (cachedResult !== null) {\n logger.info(COMPONENT, `[Cache HIT] ${handler.name}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: cachedResult,\n success: true,\n durationMs: Date.now() - startTime,\n };\n }\n\n logger.info(COMPONENT, `Executing tool: ${handler.name}`);\n\n // v5.0: Pre-execution scanner for dangerous commands\n if (handler.name === 'shell' || handler.name === 'code_exec') {\n const cmdArg = (args.command || args.code || args.script || '') as string;\n const scan = scanCommand(cmdArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this command\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (scan.warnings.length > 0 && scan.level === 'warn') {\n logger.warn('ToolRunner', `Pre-exec warnings for ${handler.name}: ${scan.warnings.join('; ')}`);\n }\n }\n if (handler.name === 'browser_navigate' || handler.name === 'browser_auto_nav') {\n const urlArg = (args.url || args.target || '') as string;\n const scan = scanURL(urlArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this URL\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n\n // v5.0: Shell hooks — pre-tool\n const { getCurrentSessionId } = await import('./agent.js');\n const sessionId = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const shellPre = await runPreToolShellHooks(handler.name, args, sessionId || toolCall.id, 'default', 0);\n if (!shellPre.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by shell hook: ' + (shellPre.reason || 'Hook denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (shellPre.modifiedArgs) args = shellPre.modifiedArgs;\n\n // Pre-tool hooks — plugins can block or modify args\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPreTool(toolHookPlugins, handler.name, args);\n if (!hookResult.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by hook: ' + (hookResult.reason || 'Plugin denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (hookResult.modifiedArgs) args = hookResult.modifiedArgs;\n }\n\n // Autonomy gate: check if the tool is permitted under current mode\n const autonomyResult = await checkAutonomy(handler.name, args, channel);\n if (!autonomyResult.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Action blocked by autonomy policy: ' + (autonomyResult.reason || 'Not permitted'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Shadow git checkpoint — snapshot files before mutation (fire-and-forget)\n const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'append_file', 'apply_patch']);\n\n // v4.9.0-local.7: kill-switch gate for file mutations. If the kill switch\n // is engaged, refuse write/edit/append/apply_patch so the initiative loop\n // can't keep accumulating fix-oscillations while the human hasn't resumed.\n // This closes the gap where `spawn_agent`, `autopilot`, and the pressure\n // cycle were gated but the main agent's tool path was not — meaning\n // initiative could keep rewriting the same files for hours after a kill.\n // See kill-switch.json `history` for the trigger; resume via\n // POST /api/safety/resume.\n if (MUTATING_TOOLS.has(handler.name)) {\n try {\n const { isKilled, getState } = await import('../safety/killSwitch.js');\n if (isKilled()) {\n const state = getState();\n const lastReason = state?.lastEvent?.reason ?? 'kill switch engaged';\n logger.warn(COMPONENT, `[KillSwitch] Refusing ${handler.name} — ${lastReason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: File mutation refused — kill switch is engaged (${lastReason}). ` +\n `Resume via POST /api/safety/resume after investigating the trigger, ` +\n `then retry. Do NOT retry this tool call until resumed.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* kill switch module unavailable — fall through (fail-open on infra error) */ }\n }\n\n // v4.9.0-local.8: Self-mod scope lock + staging.\n //\n // Three-layer policy when the active session has a goal tagged as\n // self-modifying (see config.autonomy.selfMod.tags):\n // 1. Writes to paths OUTSIDE config.autonomy.selfMod.target are refused\n // (prevents LARPing self-improvement by writing to ~/titan-saas etc)\n // 2. When staging is enabled, writes INSIDE target are diverted to a\n // per-goal staging directory and a `self_mod_pr` approval is filed\n // 3. The original path is stored as `targetPath` on the staging entry\n // so the human sees what would land where if they approve the PR\n //\n // This is the deeper fix for the pattern observed 2026-04-18 where a\n // \"self-healing framework\" goal completed 100% by writing to an unrelated\n // Next.js app.\n let stagedRedirect: { stagedPath: string; targetPath: string } | null = null;\n if (MUTATING_TOOLS.has(handler.name)) {\n const rawFilePath = (args.path || args.file_path || args.filePath) as string | undefined;\n if (rawFilePath) {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { decideScope } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const decision = decideScope(sid, rawFilePath);\n if (decision.action === 'reject') {\n logger.warn(COMPONENT, `[ScopeLock] Refusing ${handler.name}: ${decision.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${decision.reason}\\n\\nRewrite the path to live inside the self-mod target, OR retag the goal to remove self-mod tags, OR pause the goal and create a properly-scoped one.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (decision.action === 'stage' && decision.stagedPath && decision.targetPath) {\n stagedRedirect = { stagedPath: decision.stagedPath, targetPath: decision.targetPath };\n // Rewrite the tool args so the handler writes to staging.\n // Preserve both `path` and `file_path` variants since\n // different tools use different field names.\n if (args.path !== undefined) args.path = decision.stagedPath;\n if (args.file_path !== undefined) args.file_path = decision.stagedPath;\n if (args.filePath !== undefined) args.filePath = decision.stagedPath;\n // Ensure the staged parent dir exists; write_file may not\n // mkdir -p on all code paths.\n try {\n const { mkdirSync } = await import('fs');\n const { dirname } = await import('path');\n mkdirSync(dirname(decision.stagedPath), { recursive: true });\n } catch { /* best-effort */ }\n logger.info(COMPONENT, `[SelfModStaging] Diverting ${handler.name} → ${decision.stagedPath} (would land at ${decision.targetPath} on approval)`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `[ScopeLock] check failed (fail-open): ${(err as Error).message}`);\n }\n }\n }\n\n // v5.0: Filesystem checkpoint before destructive operations\n if (MUTATING_TOOLS.has(handler.name)) {\n const cpPaths: string[] = [];\n const cpPath = (args.path || args.file_path || args.filePath) as string;\n if (cpPath) cpPaths.push(cpPath);\n if (cpPaths.length > 0) {\n createCheckpoint(sessionId || toolCall.id, handler.name, args, cpPaths);\n }\n }\n\n if (MUTATING_TOOLS.has(handler.name)) {\n // Use the (potentially rewritten) path for shadow-git + fix-oscillation\n const filePath = (args.path || args.file_path || args.filePath) as string;\n if (filePath) {\n snapshotBeforeWrite(handler.name, filePath).catch(err =>\n logger.debug(COMPONENT, `Shadow checkpoint skipped: ${(err as Error).message}`),\n );\n // v4.9.0: fix-oscillation tracker. Same file written/edited\n // twice within 24h flags as oscillation, which feeds the\n // kill switch (3+ oscillations → kill). Best-effort — never\n // blocks the write.\n (async () => {\n try {\n const { recordFixEvent } = await import('../safety/fixOscillation.js');\n recordFixEvent({\n target: filePath,\n kind: 'file',\n detail: `${handler.name} via ${channel ?? 'unknown'}`,\n });\n } catch (err) {\n logger.debug(COMPONENT, `Fix-oscillation skipped: ${(err as Error).message}`);\n }\n })();\n }\n // v4.8.0: self-proposal capture — if this write is happening inside\n // an autonomous Soma-driven session, stash a copy for specialist\n // review. Fire-and-forget — never blocks tool execution.\n captureSelfProposalIfApplicable(handler.name, args).catch(err =>\n logger.debug(COMPONENT, `Self-proposal capture skipped: ${(err as Error).message}`),\n );\n }\n\n // Per-tool timeout lookup\n const toolTimeouts = (config.security as Record<string, unknown>).toolTimeouts as Record<string, number> | undefined;\n const baseTimeout = toolTimeouts?.[handler.name] || config.security.commandTimeout || 30000;\n\n // Retry config\n const retryConfig = (config.security as Record<string, unknown>).toolRetry as { enabled?: boolean; maxRetries?: number; backoffBaseMs?: number } | undefined;\n const retryEnabled = retryConfig?.enabled !== false;\n const maxRetries = retryConfig?.maxRetries ?? 3;\n const backoffBase = retryConfig?.backoffBaseMs ?? 1000;\n\n let lastError: Error | null = null;\n let lastErrorClass: ErrorClass = 'permanent';\n let attempt = 0;\n\n // Capture pre-execution file state for diff generation\n let preContent: string | undefined;\n let filePathForDiff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name)) {\n const pathArg = args.path as string | undefined;\n if (pathArg) {\n filePathForDiff = pathArg;\n try {\n if (existsSync(pathArg)) preContent = readFileSync(pathArg, 'utf-8');\n } catch { /* ignore read errors */ }\n }\n }\n\n // Swarm invariants — hard safety rules (fail-open)\n try {\n const { checkInvariants } = await import('../safety/invariants.js');\n const invariant = checkInvariants(handler.name, args);\n if (!invariant.pass) {\n logger.warn(COMPONENT, `[Invariant] Blocked ${handler.name}: ${invariant.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `INVARIANT_VIOLATION: ${invariant.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Invariant check failed (fail-open): ${(err as Error).message}`);\n }\n\n for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {\n try {\n // On timeout retry, double the timeout\n const timeout = (attempt > 0 && lastErrorClass === 'timeout') ? baseTimeout * 2 : baseTimeout;\n\n let result = await Promise.race([\n handler.execute(args),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(`Tool \"${handler.name}\" timed out after ${timeout}ms`)), timeout)\n ),\n ]);\n\n // Secret exfiltration guard — scan tool output before it leaves\n result = redactSecrets(result);\n\n // v5.0: PII redaction (privacy compliance)\n const config = loadConfig();\n if (config.security?.redactPII) {\n result = scanAndRedactPII(result);\n }\n\n // v5.0: Full exfiltration scan (layer 2-5) when configured\n if (config.security?.secretScan?.level === 'full') {\n const scan = fullExfilScan(result, 'tool_output');\n if (scan.blocked) {\n logger.warn('ToolRunner', `Exfiltration scan blocked ${handler.name}: ${scan.findings.map(f => f.type).join(', ')}`);\n }\n result = scan.redacted;\n }\n\n const durationMs = Date.now() - startTime;\n if (attempt > 0) {\n logger.info(COMPONENT, `Tool ${handler.name} succeeded on retry ${attempt} in ${durationMs}ms`);\n } else {\n logger.info(COMPONENT, `Tool ${handler.name} completed in ${durationMs}ms`);\n }\n\n // G1: Strip base64 image data before size check (prevents token explosion)\n let finalContent = sanitizeBase64(result);\n\n // Smart truncation — keep head + tail for large results (TITAN pattern)\n if (finalContent.length > 30000) {\n const head = finalContent.slice(0, 20000);\n const tail = finalContent.slice(-5000);\n finalContent = head + '\\n\\n[... ' + (finalContent.length - 25000) + ' chars omitted ...]\\n\\n' + tail;\n logger.info(COMPONENT, `Tool ${handler.name} output truncated: ${result.length} → ${finalContent.length} chars`);\n }\n\n // v5.0: Shell hooks — post-tool\n const shellPost = await runPostToolShellHooks(handler.name, args, finalContent, sessionId || toolCall.id, 'default', 0);\n if (shellPost !== undefined) finalContent = shellPost;\n\n // Post-tool hooks — plugins can modify result\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPostTool(toolHookPlugins, handler.name, args, { content: finalContent, success: true, durationMs });\n if (hookResult.modifiedContent !== undefined) finalContent = hookResult.modifiedContent;\n }\n\n // Cache the result for read-only tools (helper self-gates)\n cacheToolResult(handler.name, cacheArgKey, finalContent);\n\n // v4.9.0-local.8: if this write was diverted to staging, record\n // it in the self_mod_pr bundle. Fire-and-forget — must NOT block\n // the tool's return value or the agent loop.\n if (stagedRedirect) {\n (async () => {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { recordStagedWrite } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n await recordStagedWrite({\n sessionId: sid,\n toolName: handler.name,\n stagedPath: stagedRedirect!.stagedPath,\n targetPath: stagedRedirect!.targetPath,\n });\n } catch (err) {\n logger.debug(COMPONENT, `[SelfModStaging] recordStagedWrite failed: ${(err as Error).message}`);\n }\n })();\n }\n\n // Fire-and-forget telemetry + activity log\n (async () => {\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'tool_call', tool: handler.name, channel: channel ?? 'unknown' });\n if (MUTATING_TOOLS.has(handler.name)) {\n logActivity({ event: 'file_edit', tool: handler.name, path: (args.path || args.file_path || args.filePath) as string | undefined });\n }\n if (handler.name === 'web_search') {\n logActivity({ event: 'web_search', query: (args.query || args.q) as string | undefined });\n }\n if (handler.name === 'web_fetch') {\n logActivity({ event: 'web_fetch', url: (args.url || args.target) as string | undefined });\n }\n if (handler.name === 'run_eval' || handler.name === 'eval_suite') {\n logActivity({ event: 'eval_run', suite: (args.suite || args.name) as string | undefined });\n }\n } catch { /* activity log non-critical */ }\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: true, durationMs, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, true, durationMs, undefined, sid ?? undefined);\n })();\n\n // Compute inline diff for file-modifying tools\n let diff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name) && filePathForDiff && !stagedRedirect) {\n try {\n const postContent = existsSync(filePathForDiff) ? readFileSync(filePathForDiff, 'utf-8') : '';\n diff = computeUnifiedDiff(filePathForDiff, preContent ?? '', postContent);\n } catch { /* ignore diff errors */ }\n }\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: stagedRedirect\n ? `${finalContent}\\n\\n[SelfModStaging] Diverted to staging: ${stagedRedirect.stagedPath}. A human approval is pending before this lands at ${stagedRedirect.targetPath}.`\n : finalContent,\n success: true,\n durationMs,\n retryCount: attempt,\n diff,\n };\n } catch (error) {\n lastError = error as Error;\n lastErrorClass = classifyError(lastError, handler.name);\n\n // Don't retry permanent errors\n if (lastErrorClass === 'permanent') {\n break;\n }\n\n // Don't retry if this was the last attempt\n if (attempt >= maxRetries || !retryEnabled) {\n break;\n }\n\n // Exponential backoff: 1s, 2s, 4s (capped at 8s)\n const delay = Math.min(backoffBase * Math.pow(2, attempt), 8000);\n logger.warn(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}, attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message} — retrying in ${delay}ms`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n // All retries exhausted or permanent error\n const durationMs = Date.now() - startTime;\n const errorMsg = lastError?.message || 'Unknown error';\n const retryCount = attempt; // actual number of retries performed (matches success path)\n logger.error(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}${retryCount > 0 ? `, ${retryCount} retries` : ''}): ${errorMsg}`);\n\n // Fire-and-forget telemetry\n (async () => {\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: false, durationMs, errorClass: lastErrorClass, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, false, durationMs, lastErrorClass, sid ?? undefined);\n })();\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${errorMsg}`,\n success: false,\n durationMs,\n retryCount,\n errorClass: lastErrorClass,\n };\n}\n\n/** Execute multiple tool calls (in parallel where possible, with write-conflict detection) */\nexport async function executeTools(toolCalls: ToolCall[], channel?: string): Promise<ToolResult[]> {\n // Single tool — fast path\n if (toolCalls.length <= 1) {\n return Promise.all(toolCalls.map(tc => executeTool(tc, channel)));\n }\n\n // Multiple tools — use parallelTools engine with write-conflict detection\n const parallelCalls = toolCalls.map(tc => {\n let args: Record<string, unknown> = {};\n try { args = JSON.parse(tc.function.arguments); } catch { /* use empty */ }\n return { id: tc.id, name: tc.function.name, args };\n });\n\n const executor = async (name: string, args: Record<string, unknown>): Promise<string> => {\n // Build a synthetic ToolCall for executeTool\n const syntheticTc: ToolCall = {\n id: '',\n type: 'function',\n function: { name, arguments: JSON.stringify(args) },\n };\n const result = await executeTool(syntheticTc, channel);\n return result.content;\n };\n\n const parallelResults = await executeToolsParallel(parallelCalls, executor);\n\n // Map back to ToolResult format with full metadata\n return parallelResults.map(pr => ({\n toolCallId: pr.toolCallId,\n name: pr.name,\n content: pr.content,\n success: !pr.content.startsWith('Error:'),\n durationMs: 0,\n }));\n}\n\n// ── Self-proposal capture helper (v4.8.0) ────────────────────────────────\n\n/**\n * If the current write is happening in an autonomous, Soma-driven session,\n * stash a copy of the written content for specialist review. Silent no-op\n * in all other cases (user-driven edits, non-autonomous mode, or when\n * selfMod.enabled is false in config).\n */\nasync function captureSelfProposalIfApplicable(\n toolName: string,\n args: Record<string, unknown>,\n): Promise<void> {\n // Resolve what we can from the current autonomous context\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sessionId: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const sessionGoal = getSessionGoal(sessionId);\n const config = loadConfig();\n const autonomous = (config.autonomy?.mode === 'autonomous');\n const goalProposedBy = sessionGoal?.proposedBy ?? null;\n\n if (!shouldCapture({ toolName, autonomous, goalProposedBy })) return;\n\n const filePath = (args.path || args.file_path || args.filePath) as string | undefined;\n const content = (args.content || args.new_text || args.data) as string | undefined;\n if (!filePath || !content) return;\n\n captureWrite({\n toolName,\n filePath,\n content,\n sessionId,\n agentId: null, // filled by downstream if needed\n goalId: sessionGoal?.goalId ?? null,\n goalTitle: sessionGoal?.goalTitle ?? null,\n goalProposedBy,\n });\n}\n"],"mappings":"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","names":["getCurrentSessionId","config","durationMs"]}
|
package/dist/config/schema.js
CHANGED
|
@@ -597,8 +597,24 @@ const TitanConfigSchema = z.object({
|
|
|
597
597
|
memory: z.object({
|
|
598
598
|
enabled: z.boolean().default(true),
|
|
599
599
|
maxHistoryMessages: z.number().default(50),
|
|
600
|
-
/**
|
|
601
|
-
|
|
600
|
+
/**
|
|
601
|
+
* Enable semantic vector search via Ollama embeddings (Tier 2 memory).
|
|
602
|
+
*
|
|
603
|
+
* v5.4.0: default is now `true`. The vector layer (`src/memory/vectors.ts`,
|
|
604
|
+
* 566 LOC) was production-ready but unreachable behind a default-off
|
|
605
|
+
* switch — retrieval was purely literal substring matching, so any
|
|
606
|
+
* paraphrased recall failed. Flipping the default activates the
|
|
607
|
+
* existing infrastructure.
|
|
608
|
+
*
|
|
609
|
+
* Fallback contract: if the embedding model isn't available on
|
|
610
|
+
* Ollama (or the request errors), `addVector` and the vector-side
|
|
611
|
+
* of `searchMemory` silently fall back to keyword-only search.
|
|
612
|
+
* That fallback is best-effort and logged at debug level — see
|
|
613
|
+
* `isVectorSearchAvailable()` in `vectors.ts`.
|
|
614
|
+
*
|
|
615
|
+
* To opt out, set `memory.vectorSearchEnabled = false` in titan.json.
|
|
616
|
+
*/
|
|
617
|
+
vectorSearchEnabled: z.boolean().default(true),
|
|
602
618
|
/** Embedding model for vector search (must be available on Ollama) */
|
|
603
619
|
embeddingModel: z.string().default("nomic-embed-text"),
|
|
604
620
|
/** v5.0: Pluggable memory provider ('builtin' = default three-tier memory) */
|