titan-agent 6.1.0-beta.2 → 6.1.0-beta.21
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/LICENSE +1 -29
- package/README.md +1 -1
- package/dist/agent/agentLessons.js +2 -2
- package/dist/agent/agentLessons.js.map +1 -1
- package/dist/agent/autoModeClassifier.js +95 -0
- package/dist/agent/autoModeClassifier.js.map +1 -0
- package/dist/agent/durableJournal.js +2 -2
- package/dist/agent/durableJournal.js.map +1 -1
- package/dist/agent/goalDriver.js +67 -0
- package/dist/agent/goalDriver.js.map +1 -1
- package/dist/agent/goalPlanFile.js +3 -3
- package/dist/agent/goalPlanFile.js.map +1 -1
- package/dist/agent/missionLifecycle.js +2 -2
- package/dist/agent/missionLifecycle.js.map +1 -1
- package/dist/agent/subAgent.js +41 -0
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/agent/toolContract.js +49 -0
- package/dist/agent/toolContract.js.map +1 -0
- package/dist/agent/toolRunner.js +72 -11
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/agent/trajectoryRecorder.js +185 -0
- package/dist/agent/trajectoryRecorder.js.map +1 -0
- package/dist/agent/verification.js +164 -0
- package/dist/agent/verification.js.map +1 -0
- package/dist/agent/worktreeExecutor.js +207 -0
- package/dist/agent/worktreeExecutor.js.map +1 -0
- package/dist/config/schema.js +28 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/server.js +54 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/memory/facade.js +137 -0
- package/dist/memory/facade.js.map +1 -0
- package/dist/migrations/003-soma-profile-default.js +2 -2
- package/dist/migrations/003-soma-profile-default.js.map +1 -1
- package/dist/migrations/005-route-redirects.js +3 -1
- package/dist/migrations/005-route-redirects.js.map +1 -1
- package/dist/migrations/runner.js +2 -2
- package/dist/migrations/runner.js.map +1 -1
- package/dist/providers/defaultModel.js +14 -0
- package/dist/providers/defaultModel.js.map +1 -1
- package/dist/providers/router.js +4 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/providers/stub.js +185 -0
- package/dist/providers/stub.js.map +1 -0
- package/dist/skills/builtin/backup.js +1 -2
- package/dist/skills/builtin/backup.js.map +1 -1
- package/dist/skills/builtin/contracts.js +254 -0
- package/dist/skills/builtin/contracts.js.map +1 -0
- package/dist/skills/builtin/download_image.js +9 -0
- package/dist/skills/builtin/download_image.js.map +1 -1
- package/dist/skills/builtin/filesystem.js +70 -20
- package/dist/skills/builtin/filesystem.js.map +1 -1
- package/dist/skills/registry.js +7 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/storage/patterns.js +2 -2
- package/dist/storage/patterns.js.map +1 -1
- package/dist/storage/personalGallery.js +2 -2
- package/dist/storage/personalGallery.js.map +1 -1
- package/dist/storage/somaProfile.js +2 -2
- package/dist/storage/somaProfile.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +2 -2
- package/ui/dist/assets/{AuditPanel-DrDvXZ9u.js → AuditPanel-RNrvKmfX.js} +1 -1
- package/ui/dist/assets/{AutonomyPanel-BfhW-PRX.js → AutonomyPanel-D0nmt79R.js} +2 -2
- package/ui/dist/assets/{AutopilotPanel-C3yQBxAp.js → AutopilotPanel-DlcdfRRy.js} +1 -1
- package/ui/dist/assets/{AutoresearchPanel-BgpR3EcK.js → AutoresearchPanel-CXeO6kGK.js} +2 -2
- package/ui/dist/assets/BackupPanel-CKb-cDsk.js +1 -0
- package/ui/dist/assets/{BrowserPanel-BNaKZOnn.js → BrowserPanel-CDaoF6n5.js} +1 -1
- package/ui/dist/assets/CPActivity-BZbeztg1.js +1 -0
- package/ui/dist/assets/CPAgentDetail-C9fvLxKi.js +1 -0
- package/ui/dist/assets/CPAgents-BsBA9kb2.js +1 -0
- package/ui/dist/assets/CPApprovals-Di5itVIK.js +1 -0
- package/ui/dist/assets/CPCosts-DmgckHli.js +1 -0
- package/ui/dist/assets/CPDashboard-CmSb7woK.js +1 -0
- package/ui/dist/assets/{CPFiles-CUpP5I82.js → CPFiles-Dd0NGcpX.js} +1 -1
- package/ui/dist/assets/CPGoals-DeILFq0A.js +1 -0
- package/ui/dist/assets/{CPInbox-DUW_hntB.js → CPInbox-DAtidZez.js} +2 -2
- package/ui/dist/assets/CPIssueDetail-BvwoGI57.js +1 -0
- package/ui/dist/assets/CPIssues-CZ1hIKDP.js +1 -0
- package/ui/dist/assets/{CPLayout-J0fa-it4.js → CPLayout-luPwhNpA.js} +3 -3
- package/ui/dist/assets/CPOrg-BlYp_KP6.js +8 -0
- package/ui/dist/assets/CPRuns-D0HMCjIo.js +1 -0
- package/ui/dist/assets/{CPSocial-By_FStZ4.js → CPSocial-CZaGi14s.js} +2 -2
- package/ui/dist/assets/ChannelsPanel-khiBHwx3.js +1 -0
- package/ui/dist/assets/{CheckpointsPanel-DvKUd2VF.js → CheckpointsPanel-n2y3jzzT.js} +1 -1
- package/ui/dist/assets/{CommandPostHub-CQqRYBjf.js → CommandPostHub-iWf-zt5p.js} +4 -4
- package/ui/dist/assets/{CronPanel-Brmnnu8_.js → CronPanel-BkRJl8u6.js} +1 -1
- package/ui/dist/assets/{DataTable-seaktPzB.js → DataTable-DZ2cG3Cv.js} +1 -1
- package/ui/dist/assets/{DreamPanel-CO7iSdjn.js → DreamPanel-DF6l0WB-.js} +1 -1
- package/ui/dist/assets/{EmptyState-CmK9fywh.js → EmptyState-D7xwm8hA.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-2RVBKbym.js → EvalHarnessPanel-BHabu49z.js} +2 -2
- package/ui/dist/assets/{EvalPanel-CqAwhFQ5.js → EvalPanel-yAesMLSP.js} +1 -1
- package/ui/dist/assets/{FileViewer-D98m-vF5.js → FileViewer-DOWUQccc.js} +52 -14
- package/ui/dist/assets/{FilesPanel-dRQM0apb.js → FilesPanel-BjuDhqaX.js} +2 -2
- package/ui/dist/assets/FleetPanel-B0ZhS2XX.js +1 -0
- package/ui/dist/assets/{HomelabPanel-DjMUrO2B.js → HomelabPanel-D373ddvj.js} +1 -1
- package/ui/dist/assets/InfraView-CI99NZbI.js +2 -0
- package/ui/dist/assets/InlineEditableField-fDk3eGVo.js +1 -0
- package/ui/dist/assets/Input-j10hONMY.js +1 -0
- package/ui/dist/assets/IntegrationsPanel-kPa5Qq-f.js +1 -0
- package/ui/dist/assets/IntelligenceView-DeKBaAlY.js +2 -0
- package/ui/dist/assets/LearningPanel-B8tqHeEZ.js +1 -0
- package/ui/dist/assets/{LogsPanel-Bs9IsTCH.js → LogsPanel-sXc__trb.js} +1 -1
- package/ui/dist/assets/McpPanel-DEr8pZwB.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-F1P9COyY.js → MemoryGraphPanel-DZl7USJ6.js} +1 -1
- package/ui/dist/assets/MemoryWikiPanel-DTNVWvVT.js +11 -0
- package/ui/dist/assets/{MeshPanel-CgIAh5ZX.js → MeshPanel-b77zyQx4.js} +1 -1
- package/ui/dist/assets/MissionCanvas-D_quSl5F.js +69 -0
- package/ui/dist/assets/MissionChat-rQ5rUPAt.js +7 -0
- package/ui/dist/assets/MissionLibrary-Cxw7sLxv.js +5 -0
- package/ui/dist/assets/MissionStart-ChMWO1rr.js +1 -0
- package/ui/dist/assets/Modal-Dl5b6Yaj.js +1 -0
- package/ui/dist/assets/NvidiaPanel-D7qFbbeJ.js +1 -0
- package/ui/dist/assets/{OrganismPanel-CNZM3eof.js → OrganismPanel-EiQ9Gfqx.js} +1 -1
- package/ui/dist/assets/OverviewPanel-Bectj1Z5.js +1 -0
- package/ui/dist/assets/{PageHeader-llOXO43Z.js → PageHeader-CiuoyvDR.js} +1 -1
- package/ui/dist/assets/{PersonaProfilesPanel-CfsQTMra.js → PersonaProfilesPanel-BO87DSVG.js} +1 -1
- package/ui/dist/assets/{PersonasPanel-DIac_kZ3.js → PersonasPanel-BNogLwM0.js} +1 -1
- package/ui/dist/assets/RecipesPanel-DyqfWlaY.js +1 -0
- package/ui/dist/assets/{SecurityPanel-_gqMk8hJ.js → SecurityPanel-OMC-Tvbz.js} +1 -1
- package/ui/dist/assets/SelfImprovePanel-DXIfG_df.js +1 -0
- package/ui/dist/assets/{SelfProposalsPanel-DuA0zsGH.js → SelfProposalsPanel-CmgEz1TQ.js} +2 -2
- package/ui/dist/assets/SessionsPanel-gJXosEGf.js +1 -0
- package/ui/dist/assets/{SessionsTab-bFYaFvTZ.js → SessionsTab-BOVzzjTO.js} +1 -1
- package/ui/dist/assets/SettingsPanel-DifFGEvi.js +1 -0
- package/ui/dist/assets/SettingsView-BNpj0iym.js +2 -0
- package/ui/dist/assets/SkeletonLoader-CNSsfH3f.js +1 -0
- package/ui/dist/assets/{SkillsPanel-D08WcRGp.js → SkillsPanel-6fbRov-m.js} +1 -1
- package/ui/dist/assets/{SomaView-CYukE7Fp.js → SomaView-D3boPl5D.js} +1 -1
- package/ui/dist/assets/{StatCard-DT4Bx1mY.js → StatCard-2BmOlamJ.js} +1 -1
- package/ui/dist/assets/StatusBadge-CdVHF1nG.js +1 -0
- package/ui/dist/assets/Tabs-DW9ytf9H.js +1 -0
- package/ui/dist/assets/{TeamsPanel-DOBe72TK.js → TeamsPanel-CNaQ8Dbw.js} +1 -1
- package/ui/dist/assets/TelemetryPanel-DL81QlAW.js +1 -0
- package/ui/dist/assets/TitanCanvas-DLIN_wV3.js +1152 -0
- package/ui/dist/assets/ToolsView-BlChEE4n.js +2 -0
- package/ui/dist/assets/Tooltip-oR5JnwoN.js +6 -0
- package/ui/dist/assets/{TraceViewer-0bCz0x_a.js → TraceViewer-DtJXjCXV.js} +1 -1
- package/ui/dist/assets/TrainingPanel-4XMGaRN_.js +1 -0
- package/ui/dist/assets/{VoiceOverlay-B08Sw918.js → VoiceOverlay-B3fXJ-EC.js} +1 -1
- package/ui/dist/assets/{VramPanel-CJiic8y4.js → VramPanel-HisAYiqI.js} +1 -1
- package/ui/dist/assets/{WatchView-CaQMDEAy.js → WatchView-C92y0hU7.js} +1 -1
- package/ui/dist/assets/{WorkTab-D5bi2WRi.js → WorkTab-BYAWcvA1.js} +1 -1
- package/ui/dist/assets/{WorkflowsPanel-DdY6E93C.js → WorkflowsPanel-D2DiOBLA.js} +1 -1
- package/ui/dist/assets/{arrow-left-CHJyrO8S.js → arrow-left-thXQMJQ6.js} +1 -1
- package/ui/dist/assets/{briefcase-ROIEmiah.js → briefcase-ByMhDqlL.js} +1 -1
- package/ui/dist/assets/{chart-column-wFkjV3Ni.js → chart-column-CYHJVuxN.js} +1 -1
- package/ui/dist/assets/{check-BcOm5jPO.js → check-ChWLFWCx.js} +1 -1
- package/ui/dist/assets/{chevron-down-CM11GPpR.js → chevron-down-CU_O_5h3.js} +1 -1
- package/ui/dist/assets/{chevron-right-aFDDgFsL.js → chevron-right-DAiXsK24.js} +1 -1
- package/ui/dist/assets/{chevron-up-D6g7FB6i.js → chevron-up-BudRYwvy.js} +1 -1
- package/ui/dist/assets/{circle-check-big-DPz64kZW.js → circle-check-big-gkA-V2JW.js} +1 -1
- package/ui/dist/assets/{clock-BbCCaukN.js → clock-qO5kIED9.js} +1 -1
- package/ui/dist/assets/clsx-B-dksMZM.js +1 -0
- package/ui/dist/assets/{dollar-sign-Dy5-qkg3.js → dollar-sign-1gQNJK3V.js} +1 -1
- package/ui/dist/assets/{download-DjheLEns.js → download-DMD1m87c.js} +1 -1
- package/ui/dist/assets/{external-link-BgxGF7ZQ.js → external-link-ByafQkZ7.js} +1 -1
- package/ui/dist/assets/{folder-DY4eeMdo.js → folder-DeG4RuOD.js} +1 -1
- package/ui/dist/assets/{funnel-LWmcwhj-.js → funnel-wE8i5V9Y.js} +1 -1
- package/ui/dist/assets/{git-branch-CPZciK10.js → git-branch-BWSuK1LU.js} +1 -1
- package/ui/dist/assets/index-B8YEFBRH.js +277 -0
- package/ui/dist/assets/index-Cb5bSxRB.css +1 -0
- package/ui/dist/assets/{layers-Bn7bgu2Z.js → layers-tN-wIpE0.js} +1 -1
- package/ui/dist/assets/{legacy-BVPeht6t.js → legacy-BSZBdyop.js} +2 -2
- package/ui/dist/assets/{lightbulb-tzuCyEQk.js → lightbulb-TUHKCda3.js} +1 -1
- package/ui/dist/assets/{list-todo-say4NbSw.js → list-todo-DtxcUGz2.js} +1 -1
- package/ui/dist/assets/{missions-DbC8UmMc.js → missions-C3EC4uKT.js} +1 -1
- package/ui/dist/assets/{network-v9O7MmZT.js → network-ephW7nah.js} +1 -1
- package/ui/dist/assets/{pause-CZo33QlA.js → pause-DeggUVZc.js} +1 -1
- package/ui/dist/assets/{play-DbCJhc5H.js → play-Dfc0ia6v.js} +1 -1
- package/ui/dist/assets/{plug-CtpCZg2z.js → plug-Bl_6i_9u.js} +1 -1
- package/ui/dist/assets/{plus-O61rB_YF.js → plus-CQwCh7WY.js} +1 -1
- package/ui/dist/assets/{proxy-BAUTNyg6.js → proxy-BjQ4lsww.js} +1 -1
- package/ui/dist/assets/{rotate-ccw-n5WkWR51.js → rotate-ccw-CUuN2ebi.js} +1 -1
- package/ui/dist/assets/{save-D2Qh8KyN.js → save-PlTUvsN5.js} +1 -1
- package/ui/dist/assets/{search-C9SdcZJq.js → search-BHgLDT-j.js} +1 -1
- package/ui/dist/assets/{send-DZ_5M5QQ.js → send-pflQNBoT.js} +1 -1
- package/ui/dist/assets/{shield-check-DyP5PmaY.js → shield-check-CrD-s89i.js} +1 -1
- package/ui/dist/assets/{target-CWfn705s.js → target-D4oh8GME.js} +1 -1
- package/ui/dist/assets/{terminal-CdhfA0Kr.js → terminal-BvJZ1R3x.js} +1 -1
- package/ui/dist/assets/{toggle-right-CmYQLQRS.js → toggle-right-Dl7x_ed7.js} +1 -1
- package/ui/dist/assets/{trash-2-TQcEJEv4.js → trash-2-lPcFwnW5.js} +1 -1
- package/ui/dist/assets/{trending-up-Cw22bM4J.js → trending-up-DqUbZfDe.js} +1 -1
- package/ui/dist/assets/{trophy-DUF8Bcas.js → trophy-Dv6zFfKM.js} +1 -1
- package/ui/dist/assets/{users-BX_ZaByZ.js → users-NPyAWbs5.js} +1 -1
- package/ui/dist/assets/{wrench-Cfp7nYhG.js → wrench-BCKgeHj3.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/sw.js +1 -1
- package/ui/dist/assets/BackupPanel-BNxMlNIP.js +0 -1
- package/ui/dist/assets/CPActivity-CjIgehOq.js +0 -1
- package/ui/dist/assets/CPAgentDetail-oaymYRNI.js +0 -1
- package/ui/dist/assets/CPAgents-CWQ6iJfO.js +0 -1
- package/ui/dist/assets/CPApprovals-DzATBhtM.js +0 -1
- package/ui/dist/assets/CPCosts-xVQR148_.js +0 -1
- package/ui/dist/assets/CPDashboard-Dt6Jr7Zw.js +0 -1
- package/ui/dist/assets/CPGoals-Bt1a3C9T.js +0 -1
- package/ui/dist/assets/CPIssueDetail-DHxqtiuh.js +0 -1
- package/ui/dist/assets/CPIssues-D0l3fgUs.js +0 -1
- package/ui/dist/assets/CPOrg-DQ3tbYld.js +0 -8
- package/ui/dist/assets/CPRuns-0vncicUW.js +0 -1
- package/ui/dist/assets/ChannelsPanel-CiJs4gyw.js +0 -1
- package/ui/dist/assets/FleetPanel-Bg-f3_ip.js +0 -1
- package/ui/dist/assets/InfraView-CHYaZqMK.js +0 -2
- package/ui/dist/assets/InlineEditableField-BOLRKQq1.js +0 -1
- package/ui/dist/assets/Input-4pNffAw-.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-CFucvr-Q.js +0 -1
- package/ui/dist/assets/IntelligenceView-Dh-Wuf13.js +0 -2
- package/ui/dist/assets/LearningPanel-CtlougkF.js +0 -1
- package/ui/dist/assets/McpPanel-BobqpLsh.js +0 -1
- package/ui/dist/assets/MemoryWikiPanel-BfYksjoI.js +0 -11
- package/ui/dist/assets/MissionCanvas-0V_vtqiu.js +0 -50
- package/ui/dist/assets/MissionChat-9hGhc9Zw.js +0 -7
- package/ui/dist/assets/MissionLibrary-dFkT8pGp.js +0 -5
- package/ui/dist/assets/MissionStart-CZSEie0Q.js +0 -1
- package/ui/dist/assets/Modal-V2yNnkKk.js +0 -1
- package/ui/dist/assets/NvidiaPanel-qLJysI9M.js +0 -1
- package/ui/dist/assets/OverviewPanel-Be6LukJ_.js +0 -1
- package/ui/dist/assets/RecipesPanel-DDYOCx3w.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-B8o_fO30.js +0 -1
- package/ui/dist/assets/SessionsPanel-DeCBkSgY.js +0 -1
- package/ui/dist/assets/SettingsPanel-BlsRDAt7.js +0 -1
- package/ui/dist/assets/SettingsView-Dsa7Zr4e.js +0 -2
- package/ui/dist/assets/SkeletonLoader-BkQoZllJ.js +0 -1
- package/ui/dist/assets/StatusBadge-BFEHCdgi.js +0 -1
- package/ui/dist/assets/Tabs-g2Gs9sK0.js +0 -1
- package/ui/dist/assets/TelemetryPanel-DXAGuV0R.js +0 -1
- package/ui/dist/assets/TitanCanvas-DCl3gxSl.js +0 -1072
- package/ui/dist/assets/ToolsView-CnUjq0Js.js +0 -2
- package/ui/dist/assets/Tooltip-DJ99YlFF.js +0 -6
- package/ui/dist/assets/TrainingPanel-BLf_KMaE.js +0 -1
- package/ui/dist/assets/eye-off-CYR4nH5I.js +0 -6
- package/ui/dist/assets/index-PwwKjgT3.css +0 -1
- package/ui/dist/assets/index-slWylU2o.js +0 -244
- package/ui/dist/assets/loader-circle-E8aLxmMz.js +0 -6
|
@@ -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, 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';\nimport { recordJournalEvent } from './durableJournal.js';\nimport { emitAgentEvent } from './agentEvents.js';\nimport { formatCurrentDateContext } from './dateContext.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 /** Optional goalId — when set, spawn/return events are written into the\n * goal's durable journal (CP upgrade #4) so replayGoal sees them. */\n goalId?: string;\n /** Optional parent agentId — written into the journal as the spawning\n * agent so the per-agent journal shows what each agent kicked off. */\n parentAgentId?: 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 // v5.5.23: 'writer' template moved here from agent_handoff.ts ROLE_MAP.\n // Mirrors the Writer specialist defined in specialists.ts so the spawn_agent\n // tool, agent_delegate tool, and agent_team/chain tools all share one\n // canonical writer prompt. Tools chosen by what specialists.ts gives Writer.\n writer: {\n name: 'Writer',\n persona: 'voice-clone',\n tools: ['read_file', 'write_file', 'memory', 'web_search', 'web_fetch'],\n systemPrompt: `You are the Writer sub-agent. Your job is to draft publication-quality content — social posts, emails, announcements, short-form copy — in the requested voice.\n\nAvailable tools and when to use them:\n- web_search / web_fetch: gather context or prior posts when matching a voice\n- read_file / write_file: read briefs, save drafts to files when asked\n- memory: pull stored stylistic preferences and past phrasing\n\nMUST rules:\n- MUST match the voice the operator uses in prior posts/messages when one is provided\n- NEVER post publicly — draft only, return the draft for approval\n- For social posts (Facebook/X), keep under 280 chars unless explicitly asked for long-form. Hook in the first line.\n- Be concise. Cut filler. Strong verbs.\n\nReturn the final draft as the response. If you saved it to a file, also return the file path.`,\n maxRounds: 6,\n tier: 'smart',\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 // v6.0 CP upgrade #4 — durable journal: per-context replay needs to\n // see sub-agent spawns so the next process can decide whether the\n // child is still in flight or needs respawning.\n recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_spawned',\n { childName: agentName, task: config.task.slice(0, 200), depth: currentDepth },\n );\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 const dateContext = formatCurrentDateContext();\n let systemPrompt = `${titanCore}\\n\\n## Current Context\\n${dateContext}\\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 // v6.0.4 — hallucinated-tool fail-fast.\n // When the model calls a tool name that isn't in the registry,\n // executeTool() returns a structured error with the \"is not a valid\n // tool\" prefix and a hint listing real tools. A capable model\n // self-corrects on the next round. A weak / locally-quantized model\n // can ignore the hint and keep calling hallucinated names with\n // varying args (which slips past the exact-args loop detector).\n // We count these per spawn and abort once the budget is exhausted —\n // the spawn returns a structured \"cannot use tool set\" error instead\n // of grinding through maxRounds calls that all 404 at validation.\n const HALLUCINATED_TOOL_BUDGET = 2;\n let hallucinatedToolCalls = 0;\n const seenHallucinatedNames = new Set<string>();\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 + Mission Chat\n // v6.1.0-alpha.40 — also broadcast on the agentEvents bus so\n // the Mission lifecycle bridge can pick them up and turn each\n // into a desk-side activity sticky. Pre-alpha.40 the events\n // only went through `streamCallbacks.onToolCall` (Agent\n // Watcher's UI), which is a per-call closure — the Mission\n // bridge subscribes to the GLOBAL bus and never saw them, so\n // activity stickies stayed empty no matter how many web_search\n // / web_fetch / write_file calls the specialists fired.\n for (const tc of response.toolCalls!) {\n let parsedArgs: Record<string, unknown> = {};\n try { parsedArgs = JSON.parse(tc.function.arguments || '{}') as Record<string, unknown>; }\n catch { /* unparseable args → empty object */ }\n if (config.streamCallbacks?.onToolCall) {\n config.streamCallbacks.onToolCall(tc.function.name, parsedArgs);\n }\n try {\n emitAgentEvent({\n type: 'tool_call',\n agentId: agentName,\n agentName,\n timestamp: Date.now(),\n // v6.1.0-alpha.41 — include goalId so the mission\n // lifecycle bridge can correlate this tool_call\n // back to the mission room. Pre-alpha.41 the\n // event was emitted without goalId, so the bridge\n // (which short-circuits on `!data.goalId`)\n // silently dropped every one and the activity\n // stickies on the desk stayed empty.\n data: {\n goalId: config.goalId,\n name: tc.function.name,\n args: parsedArgs,\n },\n });\n } catch { /* event bus failure must never break the spawn */ }\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 // v6.0.4 — count hallucinated-tool failures separately. The\n // validation error from executeTool() carries the literal prefix\n // `Error: \"<name>\" is not a valid tool.` so we can detect it\n // without coupling to a status code.\n for (const r of toolResults) {\n if (r.success === false && r.content.includes('is not a valid tool')) {\n hallucinatedToolCalls++;\n seenHallucinatedNames.add(r.name);\n }\n }\n if (hallucinatedToolCalls >= HALLUCINATED_TOOL_BUDGET) {\n const namesList = Array.from(seenHallucinatedNames).slice(0, 5).join(', ');\n finalContent = `Error: specialist \"${agentName}\" (model ${model}) cannot use the available tool set — called ${hallucinatedToolCalls} hallucinated tool name(s) [${namesList}] in ${rounds} round(s). Consider re-routing to a stronger model or narrowing the task scope.`;\n logger.warn(\n COMPONENT,\n `[${agentName}] Hallucinated-tool budget exhausted (${hallucinatedToolCalls}/${HALLUCINATED_TOOL_BUDGET}, names=${namesList}) — aborting at round ${rounds}`,\n );\n break;\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 recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_returned',\n { childName: agentName, success: !finalContent.toLowerCase().startsWith('error') && validated, rounds, durationMs },\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 // 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 recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_returned',\n { childName: agentName, success: false, error: (err as Error).message, rounds, durationMs },\n );\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 {\n startSpawnTrajectory,\n finishSpawnTrajectory,\n type TrajectoryHandle,\n} from './trajectoryRecorder.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';\nimport { recordJournalEvent } from './durableJournal.js';\nimport { emitAgentEvent } from './agentEvents.js';\nimport { formatCurrentDateContext } from './dateContext.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 /** Optional goalId — when set, spawn/return events are written into the\n * goal's durable journal (CP upgrade #4) so replayGoal sees them. */\n goalId?: string;\n /** Optional parent agentId — written into the journal as the spawning\n * agent so the per-agent journal shows what each agent kicked off. */\n parentAgentId?: 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 // v5.5.23: 'writer' template moved here from agent_handoff.ts ROLE_MAP.\n // Mirrors the Writer specialist defined in specialists.ts so the spawn_agent\n // tool, agent_delegate tool, and agent_team/chain tools all share one\n // canonical writer prompt. Tools chosen by what specialists.ts gives Writer.\n writer: {\n name: 'Writer',\n persona: 'voice-clone',\n tools: ['read_file', 'write_file', 'memory', 'web_search', 'web_fetch'],\n systemPrompt: `You are the Writer sub-agent. Your job is to draft publication-quality content — social posts, emails, announcements, short-form copy — in the requested voice.\n\nAvailable tools and when to use them:\n- web_search / web_fetch: gather context or prior posts when matching a voice\n- read_file / write_file: read briefs, save drafts to files when asked\n- memory: pull stored stylistic preferences and past phrasing\n\nMUST rules:\n- MUST match the voice the operator uses in prior posts/messages when one is provided\n- NEVER post publicly — draft only, return the draft for approval\n- For social posts (Facebook/X), keep under 280 chars unless explicitly asked for long-form. Hook in the first line.\n- Be concise. Cut filler. Strong verbs.\n\nReturn the final draft as the response. If you saved it to a file, also return the file path.`,\n maxRounds: 6,\n tier: 'smart',\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 // beta.13 (Phase D.1) — trajectory recorder. Initialized after\n // model + tool resolution below; held nullable here so the\n // success/error/finally paths can safely finish() regardless of\n // whether init reached the post-resolution point.\n let trajectoryHandle: TrajectoryHandle | null = null;\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 // v6.0 CP upgrade #4 — durable journal: per-context replay needs to\n // see sub-agent spawns so the next process can decide whether the\n // child is still in flight or needs respawning.\n recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_spawned',\n { childName: agentName, task: config.task.slice(0, 200), depth: currentDepth },\n );\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 // beta.13 (Phase D.1) — open the trajectory file. We start the\n // record AFTER tool resolution so the start event captures the\n // exact toolset this spawn was offered. Captures only the names\n // (not full schemas) to keep the trajectory file small.\n trajectoryHandle = startSpawnTrajectory({\n spawnId: agentTrackingId,\n specialist: agentName,\n model,\n task: config.task,\n goalId: config.goalId,\n toolNames: availableTools.map(t => 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 const dateContext = formatCurrentDateContext();\n let systemPrompt = `${titanCore}\\n\\n## Current Context\\n${dateContext}\\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 // v6.0.4 — hallucinated-tool fail-fast.\n // When the model calls a tool name that isn't in the registry,\n // executeTool() returns a structured error with the \"is not a valid\n // tool\" prefix and a hint listing real tools. A capable model\n // self-corrects on the next round. A weak / locally-quantized model\n // can ignore the hint and keep calling hallucinated names with\n // varying args (which slips past the exact-args loop detector).\n // We count these per spawn and abort once the budget is exhausted —\n // the spawn returns a structured \"cannot use tool set\" error instead\n // of grinding through maxRounds calls that all 404 at validation.\n const HALLUCINATED_TOOL_BUDGET = 2;\n let hallucinatedToolCalls = 0;\n const seenHallucinatedNames = new Set<string>();\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 + Mission Chat\n // v6.1.0-alpha.40 — also broadcast on the agentEvents bus so\n // the Mission lifecycle bridge can pick them up and turn each\n // into a desk-side activity sticky. Pre-alpha.40 the events\n // only went through `streamCallbacks.onToolCall` (Agent\n // Watcher's UI), which is a per-call closure — the Mission\n // bridge subscribes to the GLOBAL bus and never saw them, so\n // activity stickies stayed empty no matter how many web_search\n // / web_fetch / write_file calls the specialists fired.\n for (const tc of response.toolCalls!) {\n let parsedArgs: Record<string, unknown> = {};\n try { parsedArgs = JSON.parse(tc.function.arguments || '{}') as Record<string, unknown>; }\n catch { /* unparseable args → empty object */ }\n if (config.streamCallbacks?.onToolCall) {\n config.streamCallbacks.onToolCall(tc.function.name, parsedArgs);\n }\n try {\n emitAgentEvent({\n type: 'tool_call',\n agentId: agentName,\n agentName,\n timestamp: Date.now(),\n // v6.1.0-alpha.41 — include goalId so the mission\n // lifecycle bridge can correlate this tool_call\n // back to the mission room. Pre-alpha.41 the\n // event was emitted without goalId, so the bridge\n // (which short-circuits on `!data.goalId`)\n // silently dropped every one and the activity\n // stickies on the desk stayed empty.\n data: {\n goalId: config.goalId,\n name: tc.function.name,\n args: parsedArgs,\n },\n });\n } catch { /* event bus failure must never break the spawn */ }\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 // v6.0.4 — count hallucinated-tool failures separately. The\n // validation error from executeTool() carries the literal prefix\n // `Error: \"<name>\" is not a valid tool.` so we can detect it\n // without coupling to a status code.\n for (const r of toolResults) {\n if (r.success === false && r.content.includes('is not a valid tool')) {\n hallucinatedToolCalls++;\n seenHallucinatedNames.add(r.name);\n }\n }\n if (hallucinatedToolCalls >= HALLUCINATED_TOOL_BUDGET) {\n const namesList = Array.from(seenHallucinatedNames).slice(0, 5).join(', ');\n finalContent = `Error: specialist \"${agentName}\" (model ${model}) cannot use the available tool set — called ${hallucinatedToolCalls} hallucinated tool name(s) [${namesList}] in ${rounds} round(s). Consider re-routing to a stronger model or narrowing the task scope.`;\n logger.warn(\n COMPONENT,\n `[${agentName}] Hallucinated-tool budget exhausted (${hallucinatedToolCalls}/${HALLUCINATED_TOOL_BUDGET}, names=${namesList}) — aborting at round ${rounds}`,\n );\n break;\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 recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_returned',\n { childName: agentName, success: !finalContent.toLowerCase().startsWith('error') && validated, rounds, durationMs },\n );\n\n // beta.13 — close the trajectory with the resolved status.\n // `done` vs `failed` follows the same rule used to compute\n // success: non-error prefix AND validated.\n if (trajectoryHandle) {\n const success = !finalContent.toLowerCase().startsWith('error') && validated;\n finishSpawnTrajectory(trajectoryHandle, {\n status: success ? 'done' : 'failed',\n content: finalContent,\n totalRounds: rounds,\n toolsUsed: [...new Set(toolsUsed)],\n });\n trajectoryHandle = null;\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 recordJournalEvent(\n { goalId: config.goalId, agentId: config.parentAgentId },\n 'subagent_returned',\n { childName: agentName, success: false, error: (err as Error).message, rounds, durationMs },\n );\n // beta.13 — close the trajectory with the failure details.\n if (trajectoryHandle) {\n finishSpawnTrajectory(trajectoryHandle, {\n status: 'failed',\n content: `Sub-agent error: ${(err as Error).message}`,\n totalRounds: rounds,\n toolsUsed: [...new Set(toolsUsed)],\n });\n trajectoryHandle = null;\n }\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 // beta.13 — defensive trajectory close in case neither\n // success nor catch handled it (e.g. an unusual exit path).\n // Marked aborted so the trajectory is clearly distinguishable\n // from clean completions.\n if (trajectoryHandle) {\n finishSpawnTrajectory(trajectoryHandle, {\n status: 'aborted',\n content: '',\n totalRounds: rounds,\n toolsUsed: [...new Set(toolsUsed)],\n });\n trajectoryHandle = null;\n }\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":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
class ToolValidationError extends Error {
|
|
3
|
+
toolName;
|
|
4
|
+
issues;
|
|
5
|
+
constructor(toolName, issues) {
|
|
6
|
+
const summary = issues.slice(0, 3).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
7
|
+
super(`Tool "${toolName}" rejected invalid arguments: ${summary}`);
|
|
8
|
+
this.name = "ToolValidationError";
|
|
9
|
+
this.toolName = toolName;
|
|
10
|
+
this.issues = issues;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function validateToolCall(contract, rawArgs) {
|
|
14
|
+
const result = contract.input.safeParse(rawArgs);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new ToolValidationError(contract.name, result.error.issues);
|
|
17
|
+
}
|
|
18
|
+
return result.data;
|
|
19
|
+
}
|
|
20
|
+
function formatValidationError(err) {
|
|
21
|
+
const issues = err.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
22
|
+
return `Tool "${err.toolName}" rejected the call because the arguments don't match its schema:
|
|
23
|
+
${issues}
|
|
24
|
+
|
|
25
|
+
Fix the arguments and try the call again.`;
|
|
26
|
+
}
|
|
27
|
+
const contractRegistry = /* @__PURE__ */ new Map();
|
|
28
|
+
function registerToolContract(contract) {
|
|
29
|
+
contractRegistry.set(contract.name, contract);
|
|
30
|
+
}
|
|
31
|
+
function getToolContract(name) {
|
|
32
|
+
return contractRegistry.get(name);
|
|
33
|
+
}
|
|
34
|
+
function listToolContracts() {
|
|
35
|
+
return Array.from(contractRegistry.values());
|
|
36
|
+
}
|
|
37
|
+
function clearToolContracts() {
|
|
38
|
+
contractRegistry.clear();
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
ToolValidationError,
|
|
42
|
+
clearToolContracts,
|
|
43
|
+
formatValidationError,
|
|
44
|
+
getToolContract,
|
|
45
|
+
listToolContracts,
|
|
46
|
+
registerToolContract,
|
|
47
|
+
validateToolCall
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=toolContract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent/toolContract.ts"],"sourcesContent":["/**\n * TITAN — Tool Contract (v6.1.0-beta.15, Phase D.3)\n *\n * Declarative input/output schemas + risk metadata for skills. Every\n * skill that opts in attaches a `ToolContract` to its `ToolHandler`;\n * the agent loop validates incoming tool calls against the contract\n * BEFORE the skill's execute() runs.\n *\n * WHY this module exists:\n *\n * - The current ToolHandler.parameters is a raw JSON-schema blob\n * and ToolHandler.execute accepts `Record<string, unknown>`. The\n * LLM can pass anything; skills get to discover the bad shape at\n * runtime, usually by crashing or returning a confusing string.\n *\n * - The Picrew/awesome-agent-harness catalog identifies declarative\n * tool contracts as a recurring pattern across reliable harnesses\n * (PydanticAI, deepagents, Google ADK, NeMo Agent Toolkit). The\n * shape is consistent: Zod or Pydantic schema in / Zod schema out\n * / risk + side-effects metadata for governance.\n *\n * - Contracts also feed the auto-mode classifier (Phase D.4):\n * riskLevel === 'safe' tool calls can run without an approval\n * prompt; 'high' calls require explicit user confirmation.\n *\n * DESIGN:\n *\n * - Contracts are STRICTLY ADDITIVE. ToolHandler.contract is\n * optional, so the 89 existing skill files don't break. The\n * contract opt-in proceeds skill-by-skill.\n *\n * - Input validation uses Zod. Skills declare a Zod schema; the\n * tool runner calls schema.safeParse() at dispatch and rejects\n * malformed args with a structured error message the LLM can\n * fix on the next round.\n *\n * - Output schemas are OPTIONAL. They power the auto-mode\n * verifier (D.4) and let consumers type-narrow execute results.\n * For skills whose output is a free-form string (most of ours),\n * skip it.\n *\n * - sideEffects + riskLevel are governance metadata. They never\n * change behavior at this layer — D.4 wires them into approval\n * gating, D.5 wires them into worktree isolation.\n *\n * TRADE-OFFS:\n *\n * - Skills authored against the old parameters: JSON-schema shape\n * can keep using it. Contracts are an opt-in upgrade. We don't\n * force-migrate the long tail (89 skill files) — only the 5-10\n * canonical skills get contracts in this beta.\n *\n * - Zod adds ~30 KB to the runtime bundle (already a dep). No new\n * dependencies introduced.\n *\n * - Validation runs synchronously on dispatch. Schemas should stay\n * simple — heavy refinements (network IO, fs reads) would slow\n * every tool call.\n *\n * FOLLOW-UP:\n *\n * - Phase D.4 reads `riskLevel` to decide whether to auto-approve\n * or prompt the user.\n * - Phase D.5 reads `sideEffects: 'destructive'` to decide which\n * skills must run inside an isolated worktree.\n * - A future `titan tools doctor` CLI command lints every\n * registered tool: which have contracts, which are still on the\n * raw JSON-schema shape, which are missing examples.\n */\n\nimport { z, type ZodTypeAny, type ZodError } from 'zod';\n\n/* ─────────────────────────── Types ─────────────────────────── */\n\n/**\n * What the tool DOES to the world. The runtime uses this to decide\n * whether the call needs an approval gate, an isolated worktree,\n * or just runs free.\n *\n * - read — pure observation (read_file, list_dir, web_fetch).\n * - write — mutates state the user can recover from (write_file\n * to a non-system path, append_file, edit_file).\n * - network — outbound network call (web_search, download_image,\n * web_fetch). Often pairs with `read`.\n * - destructive — unrecoverable or sensitive: shell, delete_file,\n * git operations, anything touching ~/.ssh, /etc, etc.\n */\nexport type SideEffect = 'read' | 'write' | 'network' | 'destructive';\n\n/**\n * Risk classification for the auto-mode classifier (Phase D.4).\n *\n * - safe — auto-run without prompting. Read-only tools + writes\n * inside the workspace.\n * - moderate — auto-run with a brief notification. Network calls,\n * writes outside the workspace.\n * - high — require explicit user approval every call. Shell,\n * destructive ops, anything touching credentials.\n */\nexport type RiskLevel = 'safe' | 'moderate' | 'high';\n\n/**\n * The declarative contract a skill exports alongside its handler.\n * Both schemas are Zod — TITAN already uses Zod for config so this\n * adds no new dependency.\n */\nexport interface ToolContract<TInput = unknown, TOutput = unknown> {\n /** Tool name — must match the ToolHandler.name it's attached to. */\n name: string;\n /** One-sentence summary used by the auto-mode classifier + docs. */\n summary: string;\n /** Zod schema for the call arguments. Validated on dispatch. */\n input: z.ZodType<TInput>;\n /**\n * Optional Zod schema for the execute return value. When set, the\n * runtime can validate the skill kept its end of the bargain.\n * Skills returning free-form strings (most of ours) can omit this.\n */\n output?: z.ZodType<TOutput>;\n /** What the tool does to the world — drives governance gates. */\n sideEffects: SideEffect[];\n /** Risk level the auto-mode classifier reads (Phase D.4). */\n riskLevel: RiskLevel;\n /**\n * Concrete example calls. Used by:\n * - Skill-author tests as fixture inputs\n * - Auto-mode classifier as positive samples for \"looks safe\"\n * - Docs / tool-author guide as ready-to-paste snippets\n *\n * Each example MUST validate against `input` — the test suite\n * for this module enforces that.\n */\n exampleCalls: Array<{\n description: string;\n args: TInput;\n }>;\n}\n\n/* ─────────────────────────── Validation ─────────────────────────── */\n\n/**\n * Structured validation error thrown by `validateToolCall` when the\n * incoming args don't match the contract's input schema. Distinct\n * class so the tool runner can catch it precisely + format a\n * helpful message for the LLM without confusing it with native\n * execute() errors.\n */\nexport class ToolValidationError extends Error {\n readonly toolName: string;\n readonly issues: ZodError['issues'];\n constructor(toolName: string, issues: ZodError['issues']) {\n const summary = issues\n .slice(0, 3)\n .map(i => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ');\n super(`Tool \"${toolName}\" rejected invalid arguments: ${summary}`);\n this.name = 'ToolValidationError';\n this.toolName = toolName;\n this.issues = issues;\n }\n}\n\n/**\n * Validate raw tool-call arguments against the contract. Returns the\n * parsed (and possibly coerced) args on success; throws\n * `ToolValidationError` on failure.\n *\n * Wrap in try/catch in the tool runner — DO NOT let validation\n * errors bubble all the way to the user. Instead, return a clear\n * \"your args were wrong, here's why\" string so the LLM corrects on\n * the next round.\n */\nexport function validateToolCall<TInput>(\n contract: ToolContract<TInput, unknown>,\n rawArgs: unknown,\n): TInput {\n const result = contract.input.safeParse(rawArgs);\n if (!result.success) {\n throw new ToolValidationError(contract.name, result.error.issues);\n }\n return result.data;\n}\n\n/**\n * Format a validation error as a clear human-readable message the\n * tool runner can return to the LLM. The LLM reads this on the next\n * round and (usually) corrects its args.\n */\nexport function formatValidationError(err: ToolValidationError): string {\n const issues = err.issues\n .map(i => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n');\n return `Tool \"${err.toolName}\" rejected the call because the arguments don't match its schema:\\n${issues}\\n\\nFix the arguments and try the call again.`;\n}\n\n/* ─────────────────────────── Registry ─────────────────────────── */\n\n/**\n * Optional global registry of contracts keyed by tool name. Skills\n * register their contract here at module-load time so other modules\n * (auto-mode classifier, docs generator, \"titan tools doctor\") can\n * iterate without depending on the full skill registry.\n *\n * Decoupled from `toolRegistry` in toolRunner.ts so a skill can opt\n * into a contract independently of how its handler is registered.\n */\nconst contractRegistry: Map<string, ToolContract> = new Map();\n\nexport function registerToolContract<T, U>(contract: ToolContract<T, U>): void {\n contractRegistry.set(contract.name, contract as unknown as ToolContract);\n}\n\nexport function getToolContract(name: string): ToolContract | undefined {\n return contractRegistry.get(name);\n}\n\nexport function listToolContracts(): ToolContract[] {\n return Array.from(contractRegistry.values());\n}\n\n/** For tests. */\nexport function clearToolContracts(): void {\n contractRegistry.clear();\n}\n"],"mappings":";AAmJO,MAAM,4BAA4B,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EACT,YAAY,UAAkB,QAA4B;AACtD,UAAM,UAAU,OACX,MAAM,GAAG,CAAC,EACV,IAAI,OAAK,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,OAAO,EAAE,EACxD,KAAK,IAAI;AACd,UAAM,SAAS,QAAQ,iCAAiC,OAAO,EAAE;AACjE,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAClB;AACJ;AAYO,SAAS,iBACZ,UACA,SACM;AACN,QAAM,SAAS,SAAS,MAAM,UAAU,OAAO;AAC/C,MAAI,CAAC,OAAO,SAAS;AACjB,UAAM,IAAI,oBAAoB,SAAS,MAAM,OAAO,MAAM,MAAM;AAAA,EACpE;AACA,SAAO,OAAO;AAClB;AAOO,SAAS,sBAAsB,KAAkC;AACpE,QAAM,SAAS,IAAI,OACd,IAAI,OAAK,OAAO,EAAE,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,OAAO,EAAE,EAC5D,KAAK,IAAI;AACd,SAAO,SAAS,IAAI,QAAQ;AAAA,EAAsE,MAAM;AAAA;AAAA;AAC5G;AAaA,MAAM,mBAA8C,oBAAI,IAAI;AAErD,SAAS,qBAA2B,UAAoC;AAC3E,mBAAiB,IAAI,SAAS,MAAM,QAAmC;AAC3E;AAEO,SAAS,gBAAgB,MAAwC;AACpE,SAAO,iBAAiB,IAAI,IAAI;AACpC;AAEO,SAAS,oBAAoC;AAChD,SAAO,MAAM,KAAK,iBAAiB,OAAO,CAAC;AAC/C;AAGO,SAAS,qBAA2B;AACvC,mBAAiB,MAAM;AAC3B;","names":[]}
|
package/dist/agent/toolRunner.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ToolValidationError,
|
|
4
|
+
validateToolCall,
|
|
5
|
+
formatValidationError
|
|
6
|
+
} from "./toolContract.js";
|
|
7
|
+
import { classifyToolCall, formatBreadcrumb } from "./autoModeClassifier.js";
|
|
2
8
|
import { appendFileSync, readFileSync, existsSync } from "fs";
|
|
3
9
|
import { TELEMETRY_EVENTS_PATH } from "../utils/constants.js";
|
|
4
10
|
import { executeToolsParallel } from "./parallelTools.js";
|
|
5
11
|
import { capToolOutput } from "./toolOutputCap.js";
|
|
6
12
|
import { runPreTool, runPostTool } from "../plugins/contextEngine.js";
|
|
13
|
+
import { run as runInIsolatedWorktree } from "./worktreeExecutor.js";
|
|
7
14
|
let toolHookPlugins = [];
|
|
8
15
|
function setToolHookPlugins(plugins) {
|
|
9
16
|
toolHookPlugins = plugins;
|
|
@@ -184,6 +191,33 @@ Please use one of the available tools.`,
|
|
|
184
191
|
}
|
|
185
192
|
}
|
|
186
193
|
}
|
|
194
|
+
let contractToUse = handler.contract;
|
|
195
|
+
if (!contractToUse) {
|
|
196
|
+
try {
|
|
197
|
+
const { getToolContract } = await import("./toolContract.js");
|
|
198
|
+
contractToUse = getToolContract(handler.name);
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (contractToUse) {
|
|
203
|
+
try {
|
|
204
|
+
args = validateToolCall(contractToUse, args);
|
|
205
|
+
} catch (validationErr) {
|
|
206
|
+
if (validationErr instanceof ToolValidationError) {
|
|
207
|
+
logger.warn(
|
|
208
|
+
"ToolRunner",
|
|
209
|
+
`Contract validation rejected ${handler.name}: ${validationErr.message}`
|
|
210
|
+
);
|
|
211
|
+
return {
|
|
212
|
+
toolCallId: toolCall.id,
|
|
213
|
+
name: handler.name,
|
|
214
|
+
content: formatValidationError(validationErr),
|
|
215
|
+
success: false,
|
|
216
|
+
durationMs: Date.now() - startTime
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
187
221
|
if (handler.parameters && typeof handler.parameters === "object") {
|
|
188
222
|
const schema = handler.parameters;
|
|
189
223
|
if (schema.required && Array.isArray(schema.required)) {
|
|
@@ -456,10 +490,19 @@ Rewrite the path to live inside the self-mod target, OR retag the goal to remove
|
|
|
456
490
|
} catch (err) {
|
|
457
491
|
logger.warn(COMPONENT, `Invariant check failed (fail-open): ${err.message}`);
|
|
458
492
|
}
|
|
493
|
+
const classification = classifyToolCall(handler.name);
|
|
494
|
+
let userApprovalWantsGate = false;
|
|
459
495
|
try {
|
|
460
|
-
const { requiresApproval
|
|
461
|
-
|
|
462
|
-
|
|
496
|
+
const { requiresApproval } = await import("../skills/builtin/approval_gates.js");
|
|
497
|
+
userApprovalWantsGate = requiresApproval(handler.name);
|
|
498
|
+
} catch (approvalErr) {
|
|
499
|
+
logger.warn(COMPONENT, `Approval-config probe failed (fail-open): ${approvalErr.message}`);
|
|
500
|
+
}
|
|
501
|
+
if (userApprovalWantsGate || classification.decision === "gate") {
|
|
502
|
+
try {
|
|
503
|
+
const { createApprovalRequest } = await import("../skills/builtin/approval_gates.js");
|
|
504
|
+
const gateReason = userApprovalWantsGate ? "user approval policy" : `classifier policy=${classification.policy} risk=${classification.riskLevel ?? "unknown"}`;
|
|
505
|
+
logger.info(COMPONENT, `[ApprovalGate] Tool "${handler.name}" requires human approval (${gateReason}) \u2014 filing request`);
|
|
463
506
|
const request = createApprovalRequest(handler.name, args, sessionId || toolCall.id);
|
|
464
507
|
if (request.status === "pending") {
|
|
465
508
|
return {
|
|
@@ -467,7 +510,6 @@ Rewrite the path to live inside the self-mod target, OR retag the goal to remove
|
|
|
467
510
|
name: handler.name,
|
|
468
511
|
content: `Awaiting approval: Tool "${handler.name}" requires human confirmation before execution. Request ID: ${request.id}. Approve with "approve ${request.id}" or deny with "deny ${request.id}".`,
|
|
469
512
|
success: false,
|
|
470
|
-
// v5.0: Signal to the loop that this is an approval pause, not a failure
|
|
471
513
|
approvalPending: true,
|
|
472
514
|
approvalRequestId: request.id,
|
|
473
515
|
durationMs: Date.now() - startTime
|
|
@@ -482,19 +524,38 @@ Rewrite the path to live inside the self-mod target, OR retag the goal to remove
|
|
|
482
524
|
durationMs: Date.now() - startTime
|
|
483
525
|
};
|
|
484
526
|
}
|
|
527
|
+
} catch (approvalErr) {
|
|
528
|
+
logger.warn(COMPONENT, `Approval gate check failed (fail-open): ${approvalErr.message}`);
|
|
485
529
|
}
|
|
486
|
-
}
|
|
487
|
-
logger.
|
|
530
|
+
} else if (classification.decision === "auto") {
|
|
531
|
+
logger.debug(COMPONENT, formatBreadcrumb(handler.name, classification));
|
|
532
|
+
} else if (classification.decision === "notify") {
|
|
533
|
+
logger.info(COMPONENT, formatBreadcrumb(handler.name, classification));
|
|
488
534
|
}
|
|
535
|
+
const useWorktreeIsolation = config.security.useWorktreeIsolation === true && contractToUse?.sideEffects.includes("destructive") === true;
|
|
489
536
|
for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {
|
|
490
537
|
try {
|
|
491
538
|
const timeout = attempt > 0 && lastErrorClass === "timeout" ? baseTimeout * 2 : baseTimeout;
|
|
539
|
+
const timeoutController = useWorktreeIsolation ? new AbortController() : void 0;
|
|
540
|
+
const timeoutError = new Error(`Tool "${handler.name}" timed out after ${timeout}ms`);
|
|
541
|
+
let timeoutHandle;
|
|
492
542
|
let result = await Promise.race([
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
543
|
+
useWorktreeIsolation ? runInIsolatedWorktree({
|
|
544
|
+
spawnId: toolCall.id,
|
|
545
|
+
args,
|
|
546
|
+
execute: handler.execute,
|
|
547
|
+
baseCwd: process.cwd(),
|
|
548
|
+
signal: timeoutController?.signal
|
|
549
|
+
}) : handler.execute(args),
|
|
550
|
+
new Promise((_, reject) => {
|
|
551
|
+
timeoutHandle = setTimeout(() => {
|
|
552
|
+
timeoutController?.abort(timeoutError);
|
|
553
|
+
reject(timeoutError);
|
|
554
|
+
}, timeout);
|
|
555
|
+
})
|
|
556
|
+
]).finally(() => {
|
|
557
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
558
|
+
});
|
|
498
559
|
result = redactSecrets(result);
|
|
499
560
|
const config2 = loadConfig();
|
|
500
561
|
if (config2.security?.redactPII) {
|