titan-agent 5.4.2 → 5.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/agent.js +9 -5
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +7 -3
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/checkpoint.js +2 -2
- package/dist/agent/checkpoint.js.map +1 -1
- package/dist/agent/commandPost.js +3 -3
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/goalProposer.js +2 -2
- package/dist/agent/goalProposer.js.map +1 -1
- package/dist/agent/goals.js +3 -3
- package/dist/agent/goals.js.map +1 -1
- package/dist/agent/planner.js +4 -4
- package/dist/agent/planner.js.map +1 -1
- package/dist/agent/userProfile.js +2 -2
- package/dist/agent/userProfile.js.map +1 -1
- package/dist/cli/doctor.js +33 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/onboard.js +4 -4
- package/dist/cli/onboard.js.map +1 -1
- package/dist/config/config.js +3 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +8 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/routes/adminRouter.js +500 -0
- package/dist/gateway/routes/adminRouter.js.map +1 -0
- package/dist/gateway/routes/agents.js +231 -0
- package/dist/gateway/routes/agents.js.map +1 -0
- package/dist/gateway/routes/agentsRouter.js +32 -0
- package/dist/gateway/routes/agentsRouter.js.map +1 -0
- package/dist/gateway/routes/checkpoints.js +41 -0
- package/dist/gateway/routes/checkpoints.js.map +1 -0
- package/dist/gateway/routes/commandPost.js +755 -0
- package/dist/gateway/routes/commandPost.js.map +1 -0
- package/dist/gateway/routes/companies.js +166 -0
- package/dist/gateway/routes/companies.js.map +1 -0
- package/dist/gateway/routes/files.js +295 -0
- package/dist/gateway/routes/files.js.map +1 -0
- package/dist/gateway/routes/hardwareRouter.js +151 -0
- package/dist/gateway/routes/hardwareRouter.js.map +1 -0
- package/dist/gateway/routes/mcpRouter.js +88 -0
- package/dist/gateway/routes/mcpRouter.js.map +1 -0
- package/dist/gateway/routes/mesh.js +464 -0
- package/dist/gateway/routes/mesh.js.map +1 -0
- package/dist/gateway/routes/metricsRouter.js +131 -0
- package/dist/gateway/routes/metricsRouter.js.map +1 -0
- package/dist/gateway/routes/organism.js +82 -0
- package/dist/gateway/routes/organism.js.map +1 -0
- package/dist/gateway/routes/paperclip.js +101 -0
- package/dist/gateway/routes/paperclip.js.map +1 -0
- package/dist/gateway/routes/sessions.js +227 -0
- package/dist/gateway/routes/sessions.js.map +1 -0
- package/dist/gateway/routes/skills.js +295 -0
- package/dist/gateway/routes/skills.js.map +1 -0
- package/dist/gateway/routes/socialRouter.js +145 -0
- package/dist/gateway/routes/socialRouter.js.map +1 -0
- package/dist/gateway/routes/systemRouter.js +220 -0
- package/dist/gateway/routes/systemRouter.js.map +1 -0
- package/dist/gateway/routes/teamsRecipes.js +297 -0
- package/dist/gateway/routes/teamsRecipes.js.map +1 -0
- package/dist/gateway/routes/tests.js +401 -0
- package/dist/gateway/routes/tests.js.map +1 -0
- package/dist/gateway/routes/traces.js +33 -0
- package/dist/gateway/routes/traces.js.map +1 -0
- package/dist/gateway/routes/voiceRouter.js +770 -0
- package/dist/gateway/routes/voiceRouter.js.map +1 -0
- package/dist/gateway/routes/watchRouter.js +131 -0
- package/dist/gateway/routes/watchRouter.js.map +1 -0
- package/dist/gateway/server.js +1179 -7379
- package/dist/gateway/server.js.map +1 -1
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/registry.js.map +1 -1
- package/dist/memory/episodic.js +2 -2
- package/dist/memory/episodic.js.map +1 -1
- package/dist/memory/learning.js +3 -3
- package/dist/memory/learning.js.map +1 -1
- package/dist/memory/memory.js +3 -3
- package/dist/memory/memory.js.map +1 -1
- package/dist/organism/drives.js +2 -2
- package/dist/organism/drives.js.map +1 -1
- package/dist/providers/errorTaxonomy.js +13 -0
- package/dist/providers/errorTaxonomy.js.map +1 -1
- package/dist/providers/ollama.js +3 -1
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai_compat.js +4 -3
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +13 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/safety/killSwitch.js +2 -2
- package/dist/safety/killSwitch.js.map +1 -1
- package/dist/skills/builtin/agent_debate.js +2 -2
- package/dist/skills/builtin/agent_debate.js.map +1 -1
- package/dist/skills/builtin/apply_patch.js +3 -3
- package/dist/skills/builtin/apply_patch.js.map +1 -1
- package/dist/skills/builtin/shell.js +2 -2
- package/dist/skills/builtin/shell.js.map +1 -1
- package/dist/skills/builtin/voice_control.js +49 -0
- package/dist/skills/builtin/voice_control.js.map +1 -0
- package/dist/skills/builtin/widget_gallery.js +6 -1
- package/dist/skills/builtin/widget_gallery.js.map +1 -1
- package/dist/skills/registry.js +15 -4
- package/dist/skills/registry.js.map +1 -1
- package/dist/storage/JsonStorage.js +4 -4
- package/dist/storage/JsonStorage.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/helpers.js +3 -1
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/lifecycle.js +86 -0
- package/dist/utils/lifecycle.js.map +1 -0
- package/dist/voice/bridge.js +136 -0
- package/dist/voice/bridge.js.map +1 -0
- package/docs/COO-MASTER-PLAN-2026-05-02.md +474 -0
- package/docs/HANDOFF/2026-04-29.md +141 -0
- package/docs/HANDOFF-2026-04-30.md +144 -0
- package/docs/HANDOFF-2026-05-03.md +114 -0
- package/docs/adr/2026-04-29-widget-pipeline-traceability.md +49 -0
- package/docs/agent-memory/README.md +45 -0
- package/docs/agent-memory/commands.md +100 -0
- package/docs/agent-memory/context-tree.md +101 -0
- package/docs/agent-memory/current-state.md +54 -0
- package/docs/agent-memory/decisions.md +78 -0
- package/docs/agent-memory/known-issues.md +76 -0
- package/docs/agent-memory/reflections.md +52 -0
- package/docs/agent-memory/skills-candidates.md +80 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-audit.md +256 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-test-plan.md +396 -0
- package/docs/superpowers/plans/2026-04-29-fix-all-prs.md +251 -0
- package/docs/superpowers/plans/2026-04-29-gitnexus-gap-remediation.md +969 -0
- package/package.json +5 -2
- package/ui/dist/assets/{AuditPanel-CM6Wg9hO.js → AuditPanel-VzSndmDN.js} +2 -2
- package/ui/dist/assets/{AutonomyPanel-CESx3ANg.js → AutonomyPanel-BiFouzAV.js} +2 -2
- package/ui/dist/assets/AutopilotPanel-fjOfM668.js +1 -0
- package/ui/dist/assets/{AutoresearchPanel-DR47NqT5.js → AutoresearchPanel-CVCxzAH3.js} +2 -2
- package/ui/dist/assets/BackupPanel-CHVTG--q.js +1 -0
- package/ui/dist/assets/{BrowserPanel-C15x9bLn.js → BrowserPanel-D5mvMKFU.js} +2 -2
- package/ui/dist/assets/CPActivity-B12mt35m.js +1 -0
- package/ui/dist/assets/CPAgentDetail-DsdShc-1.js +1 -0
- package/ui/dist/assets/CPAgents-j_7C-oQV.js +1 -0
- package/ui/dist/assets/CPApprovals-BShKSX9X.js +1 -0
- package/ui/dist/assets/CPCosts-CKPlhBDs.js +1 -0
- package/ui/dist/assets/CPDashboard-11c0nkxK.js +1 -0
- package/ui/dist/assets/CPFiles-BhLEOnXy.js +1 -0
- package/ui/dist/assets/CPGoals-Bi3t1b2P.js +1 -0
- package/ui/dist/assets/CPInbox-Bbr7khp6.js +11 -0
- package/ui/dist/assets/CPIssueDetail-DSdgNK8r.js +1 -0
- package/ui/dist/assets/CPIssues-DDEVKhX6.js +1 -0
- package/ui/dist/assets/CPLayout-DgPOfyGv.js +17 -0
- package/ui/dist/assets/CPOrg-Df73RrRJ.js +8 -0
- package/ui/dist/assets/CPRuns-ByioAz8w.js +1 -0
- package/ui/dist/assets/{CPSocial-nb-j7sOE.js → CPSocial-Dlnr_w1X.js} +2 -2
- package/ui/dist/assets/ChannelsPanel-DQjQCTK5.js +1 -0
- package/ui/dist/assets/CheckpointsPanel-C4vKjlAJ.js +1 -0
- package/ui/dist/assets/CommandPostHub-C9pp5Giq.js +24 -0
- package/ui/dist/assets/CronPanel-C6bzUfrD.js +1 -0
- package/ui/dist/assets/DaemonPanel-BA5Tb_UO.js +1 -0
- package/ui/dist/assets/{DataTable-B2Ma8hfi.js → DataTable-CH7IYJJh.js} +1 -1
- package/ui/dist/assets/{EmptyState-CcKyk5Yn.js → EmptyState-jU6yNDnF.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-BqtMc1ZM.js → EvalHarnessPanel-DnYqredY.js} +2 -2
- package/ui/dist/assets/EvalPanel-ChO7CD1r.js +1 -0
- package/ui/dist/assets/{FilesPanel-3QKvrWPo.js → FilesPanel-CaUkv2is.js} +2 -2
- package/ui/dist/assets/FleetPanel-DC_5uj0N.js +1 -0
- package/ui/dist/assets/{HomelabPanel-DhrjTX9m.js → HomelabPanel-CE5PGRpL.js} +2 -2
- package/ui/dist/assets/InfraView-C-uSlvb9.js +2 -0
- package/ui/dist/assets/InlineEditableField-BMQjiE6-.js +1 -0
- package/ui/dist/assets/Input-Bu_b3qmY.js +1 -0
- package/ui/dist/assets/IntegrationsPanel-DsYpAq43.js +1 -0
- package/ui/dist/assets/IntelligenceView-DUdIO1K7.js +2 -0
- package/ui/dist/assets/LearningPanel-UpQZC-mA.js +1 -0
- package/ui/dist/assets/LogsPanel-ClXJ4fcr.js +1 -0
- package/ui/dist/assets/McpPanel-JKgtIERQ.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-Bzvjmzvk.js → MemoryGraphPanel-Bo2OrvA6.js} +2 -2
- package/ui/dist/assets/MemoryWikiPanel-BqJ1AmYm.js +11 -0
- package/ui/dist/assets/{MeshPanel-C3LJSlht.js → MeshPanel-BJVGYvwk.js} +2 -2
- package/ui/dist/assets/Modal-CAAooiZU.js +1 -0
- package/ui/dist/assets/NvidiaPanel-BtCg3G4w.js +1 -0
- package/ui/dist/assets/OrganismPanel-DgrTTzcF.js +1 -0
- package/ui/dist/assets/OverviewPanel-rVav1Hox.js +1 -0
- package/ui/dist/assets/{PageHeader-BimceqQo.js → PageHeader-CnZtP8ek.js} +1 -1
- package/ui/dist/assets/PaperclipPanel-C-FKdhiF.js +1 -0
- package/ui/dist/assets/{PersonasPanel-L1j78p6H.js → PersonasPanel-BmlxokfB.js} +1 -1
- package/ui/dist/assets/RecipesPanel-BNKKChis.js +1 -0
- package/ui/dist/assets/SecurityPanel-I7JRHiNy.js +1 -0
- package/ui/dist/assets/SelfImprovePanel-u9h0Lt3p.js +1 -0
- package/ui/dist/assets/{SelfProposalsPanel-lNmiDThB.js → SelfProposalsPanel-DKl9iBjM.js} +2 -2
- package/ui/dist/assets/SessionsPanel-BhRiWI_g.js +1 -0
- package/ui/dist/assets/{SessionsTab-JQbltWww.js → SessionsTab-Bk08wyeY.js} +1 -1
- package/ui/dist/assets/SettingsPanel-haLfmG2k.js +1 -0
- package/ui/dist/assets/SettingsView--gi3fxI8.js +2 -0
- package/ui/dist/assets/{SkeletonLoader-atQtpcF5.js → SkeletonLoader-B5v09EF_.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-DlFs2ih7.js → SkillsPanel-BlAHFLcQ.js} +1 -1
- package/ui/dist/assets/SomaView-CExtS3zw.js +5 -0
- package/ui/dist/assets/{StatCard-DciE_Iqc.js → StatCard-BIsyMybM.js} +1 -1
- package/ui/dist/assets/{StatusBadge-BtfSPoW2.js → StatusBadge-D5nU7El8.js} +1 -1
- package/ui/dist/assets/Tabs-BBYZrBI8.js +1 -0
- package/ui/dist/assets/TeamsPanel-LPXJg823.js +1 -0
- package/ui/dist/assets/TelemetryPanel-EqpRBmOI.js +1 -0
- package/ui/dist/assets/TitanCanvas-BCbWnLMd.js +985 -0
- package/ui/dist/assets/ToolsView-CeP0Zz-N.js +2 -0
- package/ui/dist/assets/{Tooltip-70UK0E2I.js → Tooltip-BSO2XVpF.js} +1 -1
- package/ui/dist/assets/TraceViewer-BKI7o5B0.js +1 -0
- package/ui/dist/assets/TrainingPanel-c-RhjdE1.js +1 -0
- package/ui/dist/assets/VoiceOverlay-D-gc58b0.js +27 -0
- package/ui/dist/assets/VramPanel-C6xc7zgd.js +1 -0
- package/ui/dist/assets/{WatchView-C-sGFpVy.js → WatchView-dqBVCVH0.js} +1 -1
- package/ui/dist/assets/WorkTab-CBoLNrTM.js +1 -0
- package/ui/dist/assets/{WorkflowsPanel-CvgQU1xI.js → WorkflowsPanel-BAnSTOYe.js} +2 -2
- package/ui/dist/assets/approvalHeadline-DB9SgR-9.js +1 -0
- package/ui/dist/assets/{arrow-left-DwqHtJiU.js → arrow-left-5chqas7J.js} +1 -1
- package/ui/dist/assets/briefcase-D4vLzudp.js +6 -0
- package/ui/dist/assets/{chart-column-BtNO6sRy.js → chart-column-CdFlBpoP.js} +1 -1
- package/ui/dist/assets/check-Bpm1IONe.js +6 -0
- package/ui/dist/assets/chevron-down-D7OLjvuD.js +6 -0
- package/ui/dist/assets/chevron-right-aQEw2mUW.js +6 -0
- package/ui/dist/assets/chevron-up-C5g6pEj8.js +6 -0
- package/ui/dist/assets/{circle-check-big-DZRE_MbN.js → circle-check-big-fPhEdP88.js} +1 -1
- package/ui/dist/assets/clock-CTsgP_Sn.js +6 -0
- package/ui/dist/assets/{dollar-sign-aVG3a5eL.js → dollar-sign-CudFVYFc.js} +1 -1
- package/ui/dist/assets/{download-BxiWJU4G.js → download-DZRxDn67.js} +1 -1
- package/ui/dist/assets/external-link-BZ0y_Ahx.js +6 -0
- package/ui/dist/assets/{eye-off-CkgfFYhm.js → eye-off-BmJF0YYx.js} +1 -1
- package/ui/dist/assets/folder-DA43TRCm.js +11 -0
- package/ui/dist/assets/{funnel-PkLdxKyC.js → funnel-J3mULzrz.js} +1 -1
- package/ui/dist/assets/{git-branch-BM-Gw95X.js → git-branch-oHibJqDq.js} +1 -1
- package/ui/dist/assets/{index-D0RJ8701.css → index-BR0vfkIi.css} +1 -1
- package/ui/dist/assets/{index-CahJbWSR.js → index-DzwowwSI.js} +20 -20
- package/ui/dist/assets/{layers-BuGf4FIJ.js → layers-DsyEyu7z.js} +1 -1
- package/ui/dist/assets/{legacy-CR6o4t-y.js → legacy-8ITl64sV.js} +1 -1
- package/ui/dist/assets/{lightbulb-n8gc_XAL.js → lightbulb-C54Ske-p.js} +1 -1
- package/ui/dist/assets/list-todo-Cnd4rdoK.js +6 -0
- package/ui/dist/assets/loader-circle-1YOBsoQp.js +6 -0
- package/ui/dist/assets/network-DbGDAdrn.js +6 -0
- package/ui/dist/assets/{pause-DCV52koX.js → pause-CYhO_uQo.js} +1 -1
- package/ui/dist/assets/{play-CcJ9BnCh.js → play-DVY9c5Ck.js} +1 -1
- package/ui/dist/assets/{plug-CfWBXfCl.js → plug-BcXjlPUL.js} +1 -1
- package/ui/dist/assets/plus-Csu2v9GN.js +6 -0
- package/ui/dist/assets/{proxy-CzZDfLmm.js → proxy-DxS2_9D7.js} +1 -1
- package/ui/dist/assets/rotate-ccw-Co-_W04j.js +6 -0
- package/ui/dist/assets/save-Btx-kpoW.js +6 -0
- package/ui/dist/assets/search-0hXTwEZR.js +6 -0
- package/ui/dist/assets/send-TEpapzQR.js +6 -0
- package/ui/dist/assets/shield-check-DjBJXZUr.js +6 -0
- package/ui/dist/assets/{square-DJpUhlxi.js → square-OweUvjP-.js} +1 -1
- package/ui/dist/assets/{target-DWcmM_9m.js → target-BRW80Xer.js} +1 -1
- package/ui/dist/assets/terminal-BtiqJ628.js +16 -0
- package/ui/dist/assets/{toggle-right-YusFQ69L.js → toggle-right-CKtSrl28.js} +1 -1
- package/ui/dist/assets/{trash-2-CK7yQ55V.js → trash-2-DgWrHVax.js} +1 -1
- package/ui/dist/assets/{trending-up-DGjFyubC.js → trending-up-MpIrE4j6.js} +1 -1
- package/ui/dist/assets/{trophy-uQv_NgDB.js → trophy-CECuZNhX.js} +1 -1
- package/ui/dist/assets/users-dZgv4ePG.js +16 -0
- package/ui/dist/assets/wrench-CDz3xYve.js +11 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AutopilotPanel-DtEet1hJ.js +0 -1
- package/ui/dist/assets/BackupPanel-BGP8p3l3.js +0 -1
- package/ui/dist/assets/CPAgents-DYUtPzSq.js +0 -1
- package/ui/dist/assets/CPDashboard-Bf0-SyCh.js +0 -6
- package/ui/dist/assets/CPFiles-CxgxjQcO.js +0 -1
- package/ui/dist/assets/CPGoals-BsmCMTvT.js +0 -1
- package/ui/dist/assets/CPInbox-tMSbmQ9H.js +0 -11
- package/ui/dist/assets/ChannelsPanel-DP5C2OKd.js +0 -1
- package/ui/dist/assets/CheckpointsPanel-DlranVLZ.js +0 -1
- package/ui/dist/assets/CommandPostHub-BgxIa4Ev.js +0 -29
- package/ui/dist/assets/CronPanel-LoT5yKwJ.js +0 -1
- package/ui/dist/assets/DaemonPanel-DBGMqaE_.js +0 -1
- package/ui/dist/assets/EvalPanel-Bc33j0pN.js +0 -1
- package/ui/dist/assets/FleetPanel-CSsXuQYj.js +0 -1
- package/ui/dist/assets/InfraView-CR6HyrL6.js +0 -2
- package/ui/dist/assets/InlineEditableField-CnvF-yFR.js +0 -1
- package/ui/dist/assets/Input-GTHp2Rkr.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-CymCRE3T.js +0 -1
- package/ui/dist/assets/IntelligenceView-C1IHxJRC.js +0 -2
- package/ui/dist/assets/LearningPanel-DOCES3lH.js +0 -1
- package/ui/dist/assets/LogsPanel-BLnAqEaZ.js +0 -1
- package/ui/dist/assets/McpPanel-ChUzmr3z.js +0 -1
- package/ui/dist/assets/MemoryWikiPanel-Dwk3Aqwd.js +0 -11
- package/ui/dist/assets/NvidiaPanel-CeZK_-CV.js +0 -1
- package/ui/dist/assets/OrganismPanel-BB6YOiQV.js +0 -1
- package/ui/dist/assets/OverviewPanel-BmtBhQnv.js +0 -1
- package/ui/dist/assets/PaperclipPanel-C-brgwA3.js +0 -1
- package/ui/dist/assets/RecipesPanel-34lCfynJ.js +0 -1
- package/ui/dist/assets/SecurityPanel-CBTPWLj6.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-BrPbFHhG.js +0 -1
- package/ui/dist/assets/SessionsPanel-DAEYIn83.js +0 -1
- package/ui/dist/assets/SettingsPanel-CzRROAYQ.js +0 -1
- package/ui/dist/assets/SettingsView-CN7ii2uw.js +0 -2
- package/ui/dist/assets/SomaView-Ba642Oqb.js +0 -5
- package/ui/dist/assets/TeamsPanel-DKQ5z2Qe.js +0 -1
- package/ui/dist/assets/TelemetryPanel-B6KAc55Q.js +0 -1
- package/ui/dist/assets/TitanCanvas-C-s0A-lv.js +0 -1092
- package/ui/dist/assets/ToolsView-Dei0KMP0.js +0 -2
- package/ui/dist/assets/TraceViewer-BniolyBx.js +0 -1
- package/ui/dist/assets/TrainingPanel-Bz4CTPGW.js +0 -1
- package/ui/dist/assets/VoiceOverlay-CmNCrLcd.js +0 -37
- package/ui/dist/assets/VramPanel-Xh_OtRDR.js +0 -1
- package/ui/dist/assets/WorkTab-BjLNmgIK.js +0 -1
package/dist/providers/router.js
CHANGED
|
@@ -503,6 +503,7 @@ async function chat(options) {
|
|
|
503
503
|
let lastError = null;
|
|
504
504
|
const maxRetries = RETRY_CONFIG.maxRetries;
|
|
505
505
|
let compressionRetried = false;
|
|
506
|
+
let thinkingStripped = false;
|
|
506
507
|
try {
|
|
507
508
|
const backoff = shouldBackOff(providerName);
|
|
508
509
|
if (backoff) {
|
|
@@ -581,6 +582,18 @@ async function chat(options) {
|
|
|
581
582
|
logger.warn(COMPONENT, `[Router] Compression failed: ${compErr.message} \u2014 falling through`);
|
|
582
583
|
}
|
|
583
584
|
}
|
|
585
|
+
if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {
|
|
586
|
+
thinkingStripped = true;
|
|
587
|
+
const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};
|
|
588
|
+
delete providerOpts.think;
|
|
589
|
+
delete providerOpts.thinking;
|
|
590
|
+
delete providerOpts.thinking_mode;
|
|
591
|
+
delete providerOpts.budget_tokens;
|
|
592
|
+
delete providerOpts.enable_thinking;
|
|
593
|
+
options = { ...options, providerOptions: providerOpts };
|
|
594
|
+
logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED \u2014 stripped thinking flags, retrying ${providerName}/${model}`);
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
584
597
|
const errorMsg = createEnhancedErrorMessage(error, providerName, model, attempt);
|
|
585
598
|
if (classified.retryable && attempt < maxRetries) {
|
|
586
599
|
let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/router.ts"],"sourcesContent":["/**\n * TITAN — Universal Model Router\n * Routes model requests to the correct provider with failover, alias resolution,\n * and live model discovery across all configured providers (including local Ollama).\n *\n * Error Recovery Features:\n * - Exponential backoff retry for transient failures (429, 503, timeouts)\n * - Circuit breaker pattern to avoid hammering failing providers\n * - Automatic fallback to next provider in chain on persistent errors\n * - Detailed error messages including provider name and model\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk } from './base.js';\nimport { AnthropicProvider } from './anthropic.js';\nimport { OpenAIProvider } from './openai.js';\nimport { GoogleProvider } from './google.js';\nimport { OllamaProvider } from './ollama.js';\nimport { ClaudeCodeProvider } from './claudeCode.js';\nimport { OpenAICompatProvider, PROVIDER_PRESETS } from './openai_compat.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { findModelOnMesh } from '../mesh/registry.js';\nimport type { MeshPeer } from '../mesh/discovery.js';\nimport { routeTaskToNode } from '../mesh/transport.js';\nimport { randomBytes } from 'crypto';\nimport { sleep } from '../utils/helpers.js';\nimport { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } from './errorTaxonomy.js';\nimport { getExistingPool } from './credentialPool.js';\nimport { buildSmartContext } from '../agent/contextManager.js';\nimport { shouldBackOff } from './rateLimitTracker.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":"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","names":["errorMsg"]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/router.ts"],"sourcesContent":["/**\n * TITAN — Universal Model Router\n * Routes model requests to the correct provider with failover, alias resolution,\n * and live model discovery across all configured providers (including local Ollama).\n *\n * Error Recovery Features:\n * - Exponential backoff retry for transient failures (429, 503, timeouts)\n * - Circuit breaker pattern to avoid hammering failing providers\n * - Automatic fallback to next provider in chain on persistent errors\n * - Detailed error messages including provider name and model\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk } from './base.js';\nimport { AnthropicProvider } from './anthropic.js';\nimport { OpenAIProvider } from './openai.js';\nimport { GoogleProvider } from './google.js';\nimport { OllamaProvider } from './ollama.js';\nimport { ClaudeCodeProvider } from './claudeCode.js';\nimport { OpenAICompatProvider, PROVIDER_PRESETS } from './openai_compat.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { findModelOnMesh } from '../mesh/registry.js';\nimport type { MeshPeer } from '../mesh/discovery.js';\nimport { routeTaskToNode } from '../mesh/transport.js';\nimport { randomBytes } from 'crypto';\nimport { sleep } from '../utils/helpers.js';\nimport { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } from './errorTaxonomy.js';\nimport { getExistingPool } from './credentialPool.js';\nimport { buildSmartContext } from '../agent/contextManager.js';\nimport { shouldBackOff } from './rateLimitTracker.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n let thinkingStripped = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n // Gap 2: act on THINKING_NOT_SUPPORTED — strip thinking options and retry\n // once on the same provider. This handles models like titan-qwen3.5:4b\n // that return HTTP 400 \"does not support thinking\". We mutate options only\n // once so a second THINKING_NOT_SUPPORTED falls through to normal retry ladder.\n if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {\n thinkingStripped = true;\n const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};\n // Remove Ollama/OpenAI-compat thinking keys\n delete (providerOpts as Record<string, unknown>).think;\n delete (providerOpts as Record<string, unknown>).thinking;\n delete (providerOpts as Record<string, unknown>).thinking_mode;\n delete (providerOpts as Record<string, unknown>).budget_tokens;\n delete (providerOpts as Record<string, unknown>).enable_thinking;\n options = { ...options, providerOptions: providerOpts };\n logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED — stripped thinking flags, retrying ${providerName}/${model}`);\n continue;\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":"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","names":["errorMsg"]}
|
|
@@ -8,7 +8,7 @@ import { logAudit } from "../security/auditLog.js";
|
|
|
8
8
|
const COMPONENT = "KillSwitch";
|
|
9
9
|
const STATE_PATH = join(TITAN_HOME, "kill-switch.json");
|
|
10
10
|
let cache = null;
|
|
11
|
-
function
|
|
11
|
+
function mkdirIfNotExists() {
|
|
12
12
|
try {
|
|
13
13
|
mkdirSync(dirname(STATE_PATH), { recursive: true });
|
|
14
14
|
} catch {
|
|
@@ -33,7 +33,7 @@ function load() {
|
|
|
33
33
|
}
|
|
34
34
|
function save() {
|
|
35
35
|
if (!cache) return;
|
|
36
|
-
|
|
36
|
+
mkdirIfNotExists();
|
|
37
37
|
cache.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38
38
|
atomicWriteJsonFile(STATE_PATH, cache);
|
|
39
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safety/killSwitch.ts"],"sourcesContent":["/**\n * TITAN — Master Kill Switch (v4.9.0+, local hard-takeoff)\n *\n * Final backstop. When something the organism can't recover from\n * happens, this pauses everything autonomous until a human explicitly\n * resumes.\n *\n * Trigger sources:\n * - Safety drive pressure > 2.0 sustained for > 10 minutes\n * - Identity non-negotiable violation (drift detector fires\n * 'values_divergence' with high confidence)\n * - Canary eval drops > 30% on any task (silent degradation)\n * - Fix oscillation detector fires on same target 3× in 24h\n * - Human posts POST /api/safety/kill with a reason\n *\n * On trigger:\n * - Autopilot disabled in-memory (and persisted so restart doesn't\n * resurrect it)\n * - All active goals set status='paused'\n * - Specialists status='paused'\n * - SSE broadcast 'safety:killed' to all connected /watch clients\n * - Activity feed + audit log both record the trigger\n * - Any in-flight agent sessions get AbortController.abort()\n *\n * Resume:\n * - Human calls POST /api/safety/resume with a resolution note\n * - Kill-switch state flips to 'armed' (ready but not triggered)\n * - Previously-paused goals/specialists are NOT auto-unpaused —\n * Tony inspects each and flips manually. This is intentional:\n * the organism should not be trusted to self-resume without\n * human review of what caused the kill.\n *\n * Storage: <TITAN_HOME>/kill-switch.json — survives restarts.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { atomicWriteJsonFile } from '../utils/helpers.js';\nimport { dirname, join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport logger from '../utils/logger.js';\nimport { logAudit } from '../security/auditLog.js';\n\nconst COMPONENT = 'KillSwitch';\nconst STATE_PATH = join(TITAN_HOME, 'kill-switch.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type KillSwitchStatus = 'armed' | 'killed';\n\nexport type KillTrigger =\n | 'safety_pressure'\n | 'identity_violation'\n | 'canary_degradation'\n | 'fix_oscillation'\n | 'manual'\n | 'startup_preserve'; // restart preserved a prior killed state\n\nexport interface KillEvent {\n at: string;\n trigger: KillTrigger;\n reason: string;\n evidence?: string;\n /** Sub-module that fired the trigger (for audit). */\n firedBy?: string;\n}\n\nexport interface KillSwitchState {\n status: KillSwitchStatus;\n lastEvent?: KillEvent;\n /** Running log of trigger events — bounded at 50. */\n history: KillEvent[];\n /** When the state was last mutated. */\n updatedAt: string;\n /** ISO timestamp when Safety pressure first crossed the sustained threshold.\n * Used to require 10-minute sustained high-pressure before firing. */\n safetyHighSince?: string;\n /** Ring of recent fix-oscillation events (same target fixed twice within 24h)\n * used to fire the kill once the rolling count ≥ 3. */\n recentOscillations: Array<{ at: string; target: string }>;\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nlet cache: KillSwitchState | null = null;\n\nfunction ensureDir(): void {\n try { mkdirSync(dirname(STATE_PATH), { recursive: true }); } catch { /* ok */ }\n}\n\nfunction load(): KillSwitchState {\n if (cache) return cache;\n if (!existsSync(STATE_PATH)) {\n cache = freshState();\n return cache;\n }\n try {\n cache = JSON.parse(readFileSync(STATE_PATH, 'utf-8')) as KillSwitchState;\n if (!cache.history) cache.history = [];\n if (!cache.recentOscillations) cache.recentOscillations = [];\n return cache;\n } catch (err) {\n logger.warn(COMPONENT, `kill-switch.json parse failed, starting armed: ${(err as Error).message}`);\n cache = freshState();\n return cache;\n }\n}\n\nfunction save(): void {\n if (!cache) return;\n ensureDir();\n cache.updatedAt = new Date().toISOString();\n atomicWriteJsonFile(STATE_PATH, cache);\n}\n\nfunction freshState(): KillSwitchState {\n return {\n status: 'armed',\n history: [],\n recentOscillations: [],\n updatedAt: new Date().toISOString(),\n };\n}\n\n// ── Public API ───────────────────────────────────────────────────\n\n/**\n * Query — is TITAN paused? Every autonomous entry point should check\n * this before doing work:\n * - autopilot scheduler\n * - initiative checkInitiative()\n * - Soma pressure cycle (won't fire proposals while killed)\n * - spawn_agent tool (blocked while killed)\n * - self-mod auto-review / PR creation\n */\nexport function isKilled(): boolean {\n return load().status === 'killed';\n}\n\nexport function getState(): KillSwitchState {\n return { ...load() }; // shallow copy so callers can't mutate cache\n}\n\n/**\n * Fire the kill switch. Idempotent — if already killed, appends to the\n * event history but doesn't re-run side effects.\n */\nexport async function kill(trigger: KillTrigger, reason: string, opts: {\n evidence?: string;\n firedBy?: string;\n} = {}): Promise<void> {\n const state = load();\n const event: KillEvent = {\n at: new Date().toISOString(),\n trigger,\n reason,\n evidence: opts.evidence,\n firedBy: opts.firedBy,\n };\n const alreadyKilled = state.status === 'killed';\n state.status = 'killed';\n state.lastEvent = event;\n state.history.push(event);\n if (state.history.length > 50) state.history = state.history.slice(-50);\n save();\n\n logger.error(COMPONENT, `🛑 KILL SWITCH FIRED — ${trigger}: ${reason}`);\n try {\n logAudit('security_alert', opts.firedBy ?? 'system', {\n action: 'kill_switch_fired',\n trigger,\n reason,\n firstTime: !alreadyKilled,\n });\n } catch { /* audit unavailable — never block the kill path */ }\n\n if (alreadyKilled) return; // side effects only fire once\n\n // Execute the kill sequence — each step best-effort, never throws.\n await executeKillSequence(event);\n}\n\n/**\n * Resume operations after a human review. Requires a resolution note\n * that gets written to the event history for audit.\n *\n * Does NOT automatically un-pause goals or specialists — those require\n * explicit human action per goal. Intent is \"resume the organism, but\n * you (Tony) decide what work to resume.\"\n */\nexport function resume(resolutionNote: string, resumedBy: string): KillSwitchState {\n const state = load();\n const wasKilled = state.status === 'killed';\n // v4.9.0-local.7: always clear recentOscillations on resume. The human\n // has seen the evidence, acknowledged the pattern, and resumed — fresh\n // slate. Without this clear, the next same-file-write-twice would instantly\n // re-kill because the 24h window still contains the trigger events.\n // NEW oscillations occurring after resume are the real signal we want\n // to catch — not the ones that already caused the kill.\n const clearedCount = state.recentOscillations.length;\n state.recentOscillations = [];\n // Also clear safetyHighSince — the drive pressure that triggered this\n // may have resolved in the meantime; if not, the detector will re-arm it.\n delete state.safetyHighSince;\n if (wasKilled) {\n state.status = 'armed';\n state.history.push({\n at: new Date().toISOString(),\n trigger: 'manual',\n reason: `resumed by ${resumedBy}: ${resolutionNote}`,\n firedBy: resumedBy,\n });\n logger.info(COMPONENT, `Kill switch armed again by ${resumedBy}: ${resolutionNote} (cleared ${clearedCount} prior oscillations)`);\n try {\n logAudit('security_alert', resumedBy, {\n action: 'kill_switch_resumed',\n resolutionNote,\n clearedOscillations: clearedCount,\n });\n } catch { /* audit unavailable — never block resume */ }\n } else if (clearedCount > 0) {\n logger.info(COMPONENT, `Kill switch already armed; cleared ${clearedCount} recent oscillations by ${resumedBy}: ${resolutionNote}`);\n } else {\n logger.info(COMPONENT, 'Resume called but kill switch already armed and oscillations empty — no-op');\n }\n save();\n return { ...state };\n}\n\n// ── Trigger evaluators ───────────────────────────────────────────\n\nconst SAFETY_PRESSURE_THRESHOLD = 2.0;\nconst SAFETY_PRESSURE_SUSTAIN_MS = 10 * 60 * 1000; // 10 min\n// v4.13 (ancestor-extraction Sprint B): retuned from 24h/2-per-target to\n// 1h/5-per-target. Real oscillation is fast-repeating (model stuck writing\n// the same file over and over in a loop); 2 events across a whole day is\n// normal operation (e.g. two separate self-mod retries).\nconst FIX_OSCILLATION_WINDOW_MS = 60 * 60 * 1000; // was 24h\nconst FIX_OSCILLATION_COUNT_THRESHOLD = 8; // raised from 5 → 8 to tolerate normal retry loops\n\n/**\n * Path prefixes whose repeated writes should NOT trigger the fleet-wide\n * kill switch. These are staging/scratch directories where repeat writes\n * are EXPECTED during normal self-modification retry cycles:\n *\n * - self-mod-staging/ — TITAN's own self-modification PRs get retried\n * and re-applied here; 2+ writes per PR is the steady state\n * - /tmp/titan- — scratch files used by tests and probes\n *\n * Writes to PRODUCTION files still count toward oscillation detection.\n * Exemption only suppresses the kill-switch trigger; other observers\n * (logs, activity feed) still see the raw events.\n */\nconst OSCILLATION_EXEMPT_PREFIXES: string[] = [\n '/home/dj/.titan/self-mod-staging/',\n '/opt/TITAN/self-mod-staging/',\n '/tmp/titan-',\n '/home/dj/.titan/',\n '/opt/TITAN/',\n '/home/dj/titan-saas/',\n 'node_modules/',\n '.git/',\n 'dist/',\n 'coverage/',\n '/tmp/',\n];\n\nfunction isOscillationExemptTarget(target: string): boolean {\n if (!target) return false;\n // Target may be a bare path or \"file:/path\" / \"write_file:/path\" etc.\n // Normalize by finding the first \"/\" and comparing from there.\n const slashIdx = target.indexOf('/');\n const pathPart = slashIdx === -1 ? target : target.slice(slashIdx);\n return OSCILLATION_EXEMPT_PREFIXES.some(prefix => pathPart.startsWith(prefix));\n}\n\n/**\n * Evaluate the Safety drive pressure against the sustained-high\n * threshold. Call once per drive tick. Fires kill() when the drive\n * has been > threshold continuously for 10 minutes.\n */\nexport function evaluateSafetyPressure(safetyPressure: number): void {\n const state = load();\n const now = new Date();\n if (safetyPressure > SAFETY_PRESSURE_THRESHOLD) {\n if (!state.safetyHighSince) {\n state.safetyHighSince = now.toISOString();\n save();\n return;\n }\n const elapsed = now.getTime() - new Date(state.safetyHighSince).getTime();\n if (elapsed >= SAFETY_PRESSURE_SUSTAIN_MS && state.status === 'armed') {\n void kill('safety_pressure',\n `Safety drive pressure ${safetyPressure.toFixed(2)} > ${SAFETY_PRESSURE_THRESHOLD} sustained for ${Math.round(elapsed / 60_000)}m`,\n { firedBy: 'soma' });\n }\n } else if (state.safetyHighSince) {\n // Clear the sustained-timer — pressure dropped back below.\n state.safetyHighSince = undefined;\n save();\n }\n}\n\n/**\n * Record a fix-oscillation event (same target fixed twice within 24h).\n * Fires kill when ≥2 oscillations on the SAME target within 24h window.\n *\n * v4.10.0-local fix: Changed from \"3 total events anywhere\" to\n * \"2+ events on same target\" — prevents false positives when editing\n * different files (3 files each edited twice is not oscillation).\n */\nexport function recordFixOscillation(target: string): void {\n // v4.13 ancestor-extraction Sprint B: staging/scratch paths are exempt\n // from fleet-wide kill. They still get logged (below), just don't trigger.\n if (isOscillationExemptTarget(target)) {\n logger.debug(COMPONENT, `Oscillation event on exempt path \"${target.slice(0, 80)}\" — recorded, not counted`);\n return;\n }\n\n const state = load();\n const now = Date.now();\n state.recentOscillations.push({ at: new Date(now).toISOString(), target });\n state.recentOscillations = state.recentOscillations.filter(o =>\n now - new Date(o.at).getTime() < FIX_OSCILLATION_WINDOW_MS,\n );\n save();\n\n // Count oscillations per target\n const targetCounts = new Map<string, number>();\n for (const o of state.recentOscillations) {\n targetCounts.set(o.target, (targetCounts.get(o.target) || 0) + 1);\n }\n\n let maxCount = 0;\n let worstTarget = '';\n for (const [t, count] of targetCounts) {\n if (count > maxCount) {\n maxCount = count;\n worstTarget = t;\n }\n }\n\n // v4.13 ancestor-extraction (Paperclip scoped pause): BEFORE firing the\n // fleet-wide kill, try a scoped per-target pause. If the same target\n // hit >=3× in this window it's suspicious — pause THAT target for 15m\n // (write blocked, everything else continues). The full kill only fires\n // when a single target crosses the higher 5× threshold, which indicates\n // a stuck retry loop rather than occasional repeat edits.\n const SCOPED_PAUSE_THRESHOLD = 3;\n if (maxCount >= SCOPED_PAUSE_THRESHOLD && maxCount < FIX_OSCILLATION_COUNT_THRESHOLD) {\n try {\n // Lazy import to avoid circular deps at module load\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n void (async () => {\n const { pauseTarget, isTargetPaused } = await import('./scopedPause.js');\n if (!isTargetPaused(worstTarget)) {\n pauseTarget(worstTarget, 'fix_oscillation', {\n note: `${maxCount}× events in ${Math.round(FIX_OSCILLATION_WINDOW_MS / 60000)}m`,\n });\n }\n })();\n } catch { /* non-fatal */ }\n }\n\n // Fire fleet-wide kill ONLY when same non-exempt target hits\n // FIX_OSCILLATION_COUNT_THRESHOLD (5×) within FIX_OSCILLATION_WINDOW_MS\n // (1h). At that point it's a genuine stuck loop, not normal operation.\n if (maxCount >= FIX_OSCILLATION_COUNT_THRESHOLD && state.status === 'armed') {\n const totalEvents = state.recentOscillations.length;\n const uniqueTargets = targetCounts.size;\n const windowMin = Math.round(FIX_OSCILLATION_WINDOW_MS / 60000);\n void kill('fix_oscillation',\n `Target \"${worstTarget.slice(0, 60)}\" oscillated ${maxCount}× in ${windowMin}m (${totalEvents} total events across ${uniqueTargets} target(s))`,\n { firedBy: 'fix-oscillation-detector' });\n }\n}\n\n// ── Kill sequence (side effects) ─────────────────────────────────\n\nasync function executeKillSequence(event: KillEvent): Promise<void> {\n const steps: Array<{ name: string; fn: () => Promise<void> | void }> = [\n { name: 'disable-autopilot', fn: disableAutopilot },\n { name: 'pause-active-goals', fn: pauseActiveGoals },\n { name: 'pause-specialists', fn: pauseSpecialists },\n { name: 'abort-in-flight', fn: abortInFlightSessions },\n { name: 'broadcast-sse', fn: () => broadcastKill(event) },\n ];\n for (const step of steps) {\n try {\n await step.fn();\n logger.info(COMPONENT, `kill seq: ${step.name} ✓`);\n } catch (err) {\n logger.warn(COMPONENT, `kill seq: ${step.name} failed: ${(err as Error).message}`);\n }\n }\n}\n\nasync function disableAutopilot(): Promise<void> {\n try {\n // Best-effort: set in-memory flag that the scheduler checks\n const g = globalThis as unknown as { __titan_autopilot_killed?: boolean };\n g.__titan_autopilot_killed = true;\n } catch { /* ok */ }\n}\n\nasync function pauseActiveGoals(): Promise<void> {\n try {\n const { listGoals, updateGoal } = await import('../agent/goals.js');\n let paused = 0;\n for (const g of listGoals()) {\n if (g.status === 'active') {\n try { updateGoal(g.id, { status: 'paused' }); paused++; } catch { /* skip */ }\n }\n }\n logger.info(COMPONENT, `kill: paused ${paused} active goal(s)`);\n } catch (err) {\n logger.warn(COMPONENT, `kill: pauseActiveGoals unavailable: ${(err as Error).message}`);\n }\n}\n\nasync function pauseSpecialists(): Promise<void> {\n const mod = await import('../agent/commandPost.js').catch(() => null);\n if (!mod) return;\n const agents = mod.getRegisteredAgents();\n let paused = 0;\n for (const a of agents) {\n if (a.status !== 'active' && a.status !== 'idle') continue;\n try { mod.updateAgentStatus(a.id, 'paused'); paused++; } catch { /* skip */ }\n }\n logger.info(COMPONENT, `kill: paused ${paused} agent(s)`);\n}\n\nasync function abortInFlightSessions(): Promise<void> {\n // In-flight abort hooks would be registered on globalThis by the\n // agent loop; here we set a flag the loop checks each round.\n const g = globalThis as unknown as { __titan_abort_all?: boolean };\n g.__titan_abort_all = true;\n // Clear after 30s so normal operations can resume when Tony unpauses.\n setTimeout(() => { g.__titan_abort_all = false; }, 30_000).unref?.();\n}\n\nfunction broadcastKill(event: KillEvent): void {\n const g = globalThis as unknown as { __titan_sse_broadcast?: (topic: string, payload: unknown) => void };\n if (typeof g.__titan_sse_broadcast === 'function') {\n try { g.__titan_sse_broadcast('safety:killed', event); } catch { /* ok */ }\n }\n}\n\n/** Test-only cache reset. */\nexport function _resetKillSwitchCacheForTests(): void { cache = null; }\n"],"mappings":";AAkCA,SAAS,YAAY,cAA6B,iBAAiB;AACnE,SAAS,2BAA2B;AACpC,SAAS,SAAS,YAAY;AAC9B,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AACnB,SAAS,gBAAgB;AAEzB,MAAM,YAAY;AAClB,MAAM,aAAa,KAAK,YAAY,kBAAkB;AAwCtD,IAAI,QAAgC;AAEpC,SAAS,YAAkB;AACvB,MAAI;AAAE,cAAU,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EAAG,QAAQ;AAAA,EAAW;AAClF;AAEA,SAAS,OAAwB;AAC7B,MAAI,MAAO,QAAO;AAClB,MAAI,CAAC,WAAW,UAAU,GAAG;AACzB,YAAQ,WAAW;AACnB,WAAO;AAAA,EACX;AACA,MAAI;AACA,YAAQ,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AACpD,QAAI,CAAC,MAAM,QAAS,OAAM,UAAU,CAAC;AACrC,QAAI,CAAC,MAAM,mBAAoB,OAAM,qBAAqB,CAAC;AAC3D,WAAO;AAAA,EACX,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,kDAAmD,IAAc,OAAO,EAAE;AACjG,YAAQ,WAAW;AACnB,WAAO;AAAA,EACX;AACJ;AAEA,SAAS,OAAa;AAClB,MAAI,CAAC,MAAO;AACZ,YAAU;AACV,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,sBAAoB,YAAY,KAAK;AACzC;AAEA,SAAS,aAA8B;AACnC,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,SAAS,CAAC;AAAA,IACV,oBAAoB,CAAC;AAAA,IACrB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACJ;AAaO,SAAS,WAAoB;AAChC,SAAO,KAAK,EAAE,WAAW;AAC7B;AAEO,SAAS,WAA4B;AACxC,SAAO,EAAE,GAAG,KAAK,EAAE;AACvB;AAMA,eAAsB,KAAK,SAAsB,QAAgB,OAG7D,CAAC,GAAkB;AACnB,QAAM,QAAQ,KAAK;AACnB,QAAM,QAAmB;AAAA,IACrB,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC3B;AAAA,IACA;AAAA,IACA,UAAU,KAAK;AAAA,IACf,SAAS,KAAK;AAAA,EAClB;AACA,QAAM,gBAAgB,MAAM,WAAW;AACvC,QAAM,SAAS;AACf,QAAM,YAAY;AAClB,QAAM,QAAQ,KAAK,KAAK;AACxB,MAAI,MAAM,QAAQ,SAAS,GAAI,OAAM,UAAU,MAAM,QAAQ,MAAM,GAAG;AACtE,OAAK;AAEL,SAAO,MAAM,WAAW,sCAA0B,OAAO,KAAK,MAAM,EAAE;AACtE,MAAI;AACA,aAAS,kBAAkB,KAAK,WAAW,UAAU;AAAA,MACjD,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA,WAAW,CAAC;AAAA,IAChB,CAAC;AAAA,EACL,QAAQ;AAAA,EAAsD;AAE9D,MAAI,cAAe;AAGnB,QAAM,oBAAoB,KAAK;AACnC;AAUO,SAAS,OAAO,gBAAwB,WAAoC;AAC/E,QAAM,QAAQ,KAAK;AACnB,QAAM,YAAY,MAAM,WAAW;AAOnC,QAAM,eAAe,MAAM,mBAAmB;AAC9C,QAAM,qBAAqB,CAAC;AAG5B,SAAO,MAAM;AACb,MAAI,WAAW;AACX,UAAM,SAAS;AACf,UAAM,QAAQ,KAAK;AAAA,MACf,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,SAAS;AAAA,MACT,QAAQ,cAAc,SAAS,KAAK,cAAc;AAAA,MAClD,SAAS;AAAA,IACb,CAAC;AACD,WAAO,KAAK,WAAW,8BAA8B,SAAS,KAAK,cAAc,aAAa,YAAY,sBAAsB;AAChI,QAAI;AACA,eAAS,kBAAkB,WAAW;AAAA,QAClC,QAAQ;AAAA,QACR;AAAA,QACA,qBAAqB;AAAA,MACzB,CAAC;AAAA,IACL,QAAQ;AAAA,IAA+C;AAAA,EAC3D,WAAW,eAAe,GAAG;AACzB,WAAO,KAAK,WAAW,sCAAsC,YAAY,2BAA2B,SAAS,KAAK,cAAc,EAAE;AAAA,EACtI,OAAO;AACH,WAAO,KAAK,WAAW,iFAA4E;AAAA,EACvG;AACA,OAAK;AACL,SAAO,EAAE,GAAG,MAAM;AACtB;AAIA,MAAM,4BAA4B;AAClC,MAAM,6BAA6B,KAAK,KAAK;AAK7C,MAAM,4BAA4B,KAAK,KAAK;AAC5C,MAAM,kCAAkC;AAexC,MAAM,8BAAwC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAEA,SAAS,0BAA0B,QAAyB;AACxD,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,WAAW,OAAO,QAAQ,GAAG;AACnC,QAAM,WAAW,aAAa,KAAK,SAAS,OAAO,MAAM,QAAQ;AACjE,SAAO,4BAA4B,KAAK,YAAU,SAAS,WAAW,MAAM,CAAC;AACjF;AAOO,SAAS,uBAAuB,gBAA8B;AACjE,QAAM,QAAQ,KAAK;AACnB,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,iBAAiB,2BAA2B;AAC5C,QAAI,CAAC,MAAM,iBAAiB;AACxB,YAAM,kBAAkB,IAAI,YAAY;AACxC,WAAK;AACL;AAAA,IACJ;AACA,UAAM,UAAU,IAAI,QAAQ,IAAI,IAAI,KAAK,MAAM,eAAe,EAAE,QAAQ;AACxE,QAAI,WAAW,8BAA8B,MAAM,WAAW,SAAS;AACnE,WAAK;AAAA,QAAK;AAAA,QACN,yBAAyB,eAAe,QAAQ,CAAC,CAAC,MAAM,yBAAyB,kBAAkB,KAAK,MAAM,UAAU,GAAM,CAAC;AAAA,QAC/H,EAAE,SAAS,OAAO;AAAA,MAAC;AAAA,IAC3B;AAAA,EACJ,WAAW,MAAM,iBAAiB;AAE9B,UAAM,kBAAkB;AACxB,SAAK;AAAA,EACT;AACJ;AAUO,SAAS,qBAAqB,QAAsB;AAGvD,MAAI,0BAA0B,MAAM,GAAG;AACnC,WAAO,MAAM,WAAW,qCAAqC,OAAO,MAAM,GAAG,EAAE,CAAC,gCAA2B;AAC3G;AAAA,EACJ;AAEA,QAAM,QAAQ,KAAK;AACnB,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,mBAAmB,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC;AACzE,QAAM,qBAAqB,MAAM,mBAAmB;AAAA,IAAO,OACvD,MAAM,IAAI,KAAK,EAAE,EAAE,EAAE,QAAQ,IAAI;AAAA,EACrC;AACA,OAAK;AAGL,QAAM,eAAe,oBAAI,IAAoB;AAC7C,aAAW,KAAK,MAAM,oBAAoB;AACtC,iBAAa,IAAI,EAAE,SAAS,aAAa,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,EACpE;AAEA,MAAI,WAAW;AACf,MAAI,cAAc;AAClB,aAAW,CAAC,GAAG,KAAK,KAAK,cAAc;AACnC,QAAI,QAAQ,UAAU;AAClB,iBAAW;AACX,oBAAc;AAAA,IAClB;AAAA,EACJ;AAQA,QAAM,yBAAyB;AAC/B,MAAI,YAAY,0BAA0B,WAAW,iCAAiC;AAClF,QAAI;AAGA,YAAM,YAAY;AACd,cAAM,EAAE,aAAa,eAAe,IAAI,MAAM,OAAO,kBAAkB;AACvE,YAAI,CAAC,eAAe,WAAW,GAAG;AAC9B,sBAAY,aAAa,mBAAmB;AAAA,YACxC,MAAM,GAAG,QAAQ,kBAAe,KAAK,MAAM,4BAA4B,GAAK,CAAC;AAAA,UACjF,CAAC;AAAA,QACL;AAAA,MACJ,GAAG;AAAA,IACP,QAAQ;AAAA,IAAkB;AAAA,EAC9B;AAKA,MAAI,YAAY,mCAAmC,MAAM,WAAW,SAAS;AACzE,UAAM,cAAc,MAAM,mBAAmB;AAC7C,UAAM,gBAAgB,aAAa;AACnC,UAAM,YAAY,KAAK,MAAM,4BAA4B,GAAK;AAC9D,SAAK;AAAA,MAAK;AAAA,MACN,WAAW,YAAY,MAAM,GAAG,EAAE,CAAC,gBAAgB,QAAQ,WAAQ,SAAS,MAAM,WAAW,wBAAwB,aAAa;AAAA,MAClI,EAAE,SAAS,2BAA2B;AAAA,IAAC;AAAA,EAC/C;AACJ;AAIA,eAAe,oBAAoB,OAAiC;AAChE,QAAM,QAAiE;AAAA,IACnE,EAAE,MAAM,qBAAqB,IAAI,iBAAiB;AAAA,IAClD,EAAE,MAAM,sBAAsB,IAAI,iBAAiB;AAAA,IACnD,EAAE,MAAM,qBAAqB,IAAI,iBAAiB;AAAA,IAClD,EAAE,MAAM,mBAAmB,IAAI,sBAAsB;AAAA,IACrD,EAAE,MAAM,iBAAiB,IAAI,MAAM,cAAc,KAAK,EAAE;AAAA,EAC5D;AACA,aAAW,QAAQ,OAAO;AACtB,QAAI;AACA,YAAM,KAAK,GAAG;AACd,aAAO,KAAK,WAAW,aAAa,KAAK,IAAI,SAAI;AAAA,IACrD,SAAS,KAAK;AACV,aAAO,KAAK,WAAW,aAAa,KAAK,IAAI,YAAa,IAAc,OAAO,EAAE;AAAA,IACrF;AAAA,EACJ;AACJ;AAEA,eAAe,mBAAkC;AAC7C,MAAI;AAEA,UAAM,IAAI;AACV,MAAE,2BAA2B;AAAA,EACjC,QAAQ;AAAA,EAAW;AACvB;AAEA,eAAe,mBAAkC;AAC7C,MAAI;AACA,UAAM,EAAE,WAAW,WAAW,IAAI,MAAM,OAAO,mBAAmB;AAClE,QAAI,SAAS;AACb,eAAW,KAAK,UAAU,GAAG;AACzB,UAAI,EAAE,WAAW,UAAU;AACvB,YAAI;AAAE,qBAAW,EAAE,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAG;AAAA,QAAU,QAAQ;AAAA,QAAa;AAAA,MACjF;AAAA,IACJ;AACA,WAAO,KAAK,WAAW,gBAAgB,MAAM,iBAAiB;AAAA,EAClE,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,uCAAwC,IAAc,OAAO,EAAE;AAAA,EAC1F;AACJ;AAEA,eAAe,mBAAkC;AAC7C,QAAM,MAAM,MAAM,OAAO,yBAAyB,EAAE,MAAM,MAAM,IAAI;AACpE,MAAI,CAAC,IAAK;AACV,QAAM,SAAS,IAAI,oBAAoB;AACvC,MAAI,SAAS;AACb,aAAW,KAAK,QAAQ;AACpB,QAAI,EAAE,WAAW,YAAY,EAAE,WAAW,OAAQ;AAClD,QAAI;AAAE,UAAI,kBAAkB,EAAE,IAAI,QAAQ;AAAG;AAAA,IAAU,QAAQ;AAAA,IAAa;AAAA,EAChF;AACA,SAAO,KAAK,WAAW,gBAAgB,MAAM,WAAW;AAC5D;AAEA,eAAe,wBAAuC;AAGlD,QAAM,IAAI;AACV,IAAE,oBAAoB;AAEtB,aAAW,MAAM;AAAE,MAAE,oBAAoB;AAAA,EAAO,GAAG,GAAM,EAAE,QAAQ;AACvE;AAEA,SAAS,cAAc,OAAwB;AAC3C,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,0BAA0B,YAAY;AAC/C,QAAI;AAAE,QAAE,sBAAsB,iBAAiB,KAAK;AAAA,IAAG,QAAQ;AAAA,IAAW;AAAA,EAC9E;AACJ;AAGO,SAAS,gCAAsC;AAAE,UAAQ;AAAM;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safety/killSwitch.ts"],"sourcesContent":["/**\n * TITAN — Master Kill Switch (v4.9.0+, local hard-takeoff)\n *\n * Final backstop. When something the organism can't recover from\n * happens, this pauses everything autonomous until a human explicitly\n * resumes.\n *\n * Trigger sources:\n * - Safety drive pressure > 2.0 sustained for > 10 minutes\n * - Identity non-negotiable violation (drift detector fires\n * 'values_divergence' with high confidence)\n * - Canary eval drops > 30% on any task (silent degradation)\n * - Fix oscillation detector fires on same target 3× in 24h\n * - Human posts POST /api/safety/kill with a reason\n *\n * On trigger:\n * - Autopilot disabled in-memory (and persisted so restart doesn't\n * resurrect it)\n * - All active goals set status='paused'\n * - Specialists status='paused'\n * - SSE broadcast 'safety:killed' to all connected /watch clients\n * - Activity feed + audit log both record the trigger\n * - Any in-flight agent sessions get AbortController.abort()\n *\n * Resume:\n * - Human calls POST /api/safety/resume with a resolution note\n * - Kill-switch state flips to 'armed' (ready but not triggered)\n * - Previously-paused goals/specialists are NOT auto-unpaused —\n * Tony inspects each and flips manually. This is intentional:\n * the organism should not be trusted to self-resume without\n * human review of what caused the kill.\n *\n * Storage: <TITAN_HOME>/kill-switch.json — survives restarts.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { atomicWriteJsonFile } from '../utils/helpers.js';\nimport { dirname, join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport logger from '../utils/logger.js';\nimport { logAudit } from '../security/auditLog.js';\n\nconst COMPONENT = 'KillSwitch';\nconst STATE_PATH = join(TITAN_HOME, 'kill-switch.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type KillSwitchStatus = 'armed' | 'killed';\n\nexport type KillTrigger =\n | 'safety_pressure'\n | 'identity_violation'\n | 'canary_degradation'\n | 'fix_oscillation'\n | 'manual'\n | 'startup_preserve'; // restart preserved a prior killed state\n\nexport interface KillEvent {\n at: string;\n trigger: KillTrigger;\n reason: string;\n evidence?: string;\n /** Sub-module that fired the trigger (for audit). */\n firedBy?: string;\n}\n\nexport interface KillSwitchState {\n status: KillSwitchStatus;\n lastEvent?: KillEvent;\n /** Running log of trigger events — bounded at 50. */\n history: KillEvent[];\n /** When the state was last mutated. */\n updatedAt: string;\n /** ISO timestamp when Safety pressure first crossed the sustained threshold.\n * Used to require 10-minute sustained high-pressure before firing. */\n safetyHighSince?: string;\n /** Ring of recent fix-oscillation events (same target fixed twice within 24h)\n * used to fire the kill once the rolling count ≥ 3. */\n recentOscillations: Array<{ at: string; target: string }>;\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nlet cache: KillSwitchState | null = null;\n\nfunction mkdirIfNotExists(): void {\n try { mkdirSync(dirname(STATE_PATH), { recursive: true }); } catch { /* ok */ }\n}\n\nfunction load(): KillSwitchState {\n if (cache) return cache;\n if (!existsSync(STATE_PATH)) {\n cache = freshState();\n return cache;\n }\n try {\n cache = JSON.parse(readFileSync(STATE_PATH, 'utf-8')) as KillSwitchState;\n if (!cache.history) cache.history = [];\n if (!cache.recentOscillations) cache.recentOscillations = [];\n return cache;\n } catch (err) {\n logger.warn(COMPONENT, `kill-switch.json parse failed, starting armed: ${(err as Error).message}`);\n cache = freshState();\n return cache;\n }\n}\n\nfunction save(): void {\n if (!cache) return;\n mkdirIfNotExists();\n cache.updatedAt = new Date().toISOString();\n atomicWriteJsonFile(STATE_PATH, cache);\n}\n\nfunction freshState(): KillSwitchState {\n return {\n status: 'armed',\n history: [],\n recentOscillations: [],\n updatedAt: new Date().toISOString(),\n };\n}\n\n// ── Public API ───────────────────────────────────────────────────\n\n/**\n * Query — is TITAN paused? Every autonomous entry point should check\n * this before doing work:\n * - autopilot scheduler\n * - initiative checkInitiative()\n * - Soma pressure cycle (won't fire proposals while killed)\n * - spawn_agent tool (blocked while killed)\n * - self-mod auto-review / PR creation\n */\nexport function isKilled(): boolean {\n return load().status === 'killed';\n}\n\nexport function getState(): KillSwitchState {\n return { ...load() }; // shallow copy so callers can't mutate cache\n}\n\n/**\n * Fire the kill switch. Idempotent — if already killed, appends to the\n * event history but doesn't re-run side effects.\n */\nexport async function kill(trigger: KillTrigger, reason: string, opts: {\n evidence?: string;\n firedBy?: string;\n} = {}): Promise<void> {\n const state = load();\n const event: KillEvent = {\n at: new Date().toISOString(),\n trigger,\n reason,\n evidence: opts.evidence,\n firedBy: opts.firedBy,\n };\n const alreadyKilled = state.status === 'killed';\n state.status = 'killed';\n state.lastEvent = event;\n state.history.push(event);\n if (state.history.length > 50) state.history = state.history.slice(-50);\n save();\n\n logger.error(COMPONENT, `🛑 KILL SWITCH FIRED — ${trigger}: ${reason}`);\n try {\n logAudit('security_alert', opts.firedBy ?? 'system', {\n action: 'kill_switch_fired',\n trigger,\n reason,\n firstTime: !alreadyKilled,\n });\n } catch { /* audit unavailable — never block the kill path */ }\n\n if (alreadyKilled) return; // side effects only fire once\n\n // Execute the kill sequence — each step best-effort, never throws.\n await executeKillSequence(event);\n}\n\n/**\n * Resume operations after a human review. Requires a resolution note\n * that gets written to the event history for audit.\n *\n * Does NOT automatically un-pause goals or specialists — those require\n * explicit human action per goal. Intent is \"resume the organism, but\n * you (Tony) decide what work to resume.\"\n */\nexport function resume(resolutionNote: string, resumedBy: string): KillSwitchState {\n const state = load();\n const wasKilled = state.status === 'killed';\n // v4.9.0-local.7: always clear recentOscillations on resume. The human\n // has seen the evidence, acknowledged the pattern, and resumed — fresh\n // slate. Without this clear, the next same-file-write-twice would instantly\n // re-kill because the 24h window still contains the trigger events.\n // NEW oscillations occurring after resume are the real signal we want\n // to catch — not the ones that already caused the kill.\n const clearedCount = state.recentOscillations.length;\n state.recentOscillations = [];\n // Also clear safetyHighSince — the drive pressure that triggered this\n // may have resolved in the meantime; if not, the detector will re-arm it.\n delete state.safetyHighSince;\n if (wasKilled) {\n state.status = 'armed';\n state.history.push({\n at: new Date().toISOString(),\n trigger: 'manual',\n reason: `resumed by ${resumedBy}: ${resolutionNote}`,\n firedBy: resumedBy,\n });\n logger.info(COMPONENT, `Kill switch armed again by ${resumedBy}: ${resolutionNote} (cleared ${clearedCount} prior oscillations)`);\n try {\n logAudit('security_alert', resumedBy, {\n action: 'kill_switch_resumed',\n resolutionNote,\n clearedOscillations: clearedCount,\n });\n } catch { /* audit unavailable — never block resume */ }\n } else if (clearedCount > 0) {\n logger.info(COMPONENT, `Kill switch already armed; cleared ${clearedCount} recent oscillations by ${resumedBy}: ${resolutionNote}`);\n } else {\n logger.info(COMPONENT, 'Resume called but kill switch already armed and oscillations empty — no-op');\n }\n save();\n return { ...state };\n}\n\n// ── Trigger evaluators ───────────────────────────────────────────\n\nconst SAFETY_PRESSURE_THRESHOLD = 2.0;\nconst SAFETY_PRESSURE_SUSTAIN_MS = 10 * 60 * 1000; // 10 min\n// v4.13 (ancestor-extraction Sprint B): retuned from 24h/2-per-target to\n// 1h/5-per-target. Real oscillation is fast-repeating (model stuck writing\n// the same file over and over in a loop); 2 events across a whole day is\n// normal operation (e.g. two separate self-mod retries).\nconst FIX_OSCILLATION_WINDOW_MS = 60 * 60 * 1000; // was 24h\nconst FIX_OSCILLATION_COUNT_THRESHOLD = 8; // raised from 5 → 8 to tolerate normal retry loops\n\n/**\n * Path prefixes whose repeated writes should NOT trigger the fleet-wide\n * kill switch. These are staging/scratch directories where repeat writes\n * are EXPECTED during normal self-modification retry cycles:\n *\n * - self-mod-staging/ — TITAN's own self-modification PRs get retried\n * and re-applied here; 2+ writes per PR is the steady state\n * - /tmp/titan- — scratch files used by tests and probes\n *\n * Writes to PRODUCTION files still count toward oscillation detection.\n * Exemption only suppresses the kill-switch trigger; other observers\n * (logs, activity feed) still see the raw events.\n */\nconst OSCILLATION_EXEMPT_PREFIXES: string[] = [\n '/home/dj/.titan/self-mod-staging/',\n '/opt/TITAN/self-mod-staging/',\n '/tmp/titan-',\n '/home/dj/.titan/',\n '/opt/TITAN/',\n '/home/dj/titan-saas/',\n 'node_modules/',\n '.git/',\n 'dist/',\n 'coverage/',\n '/tmp/',\n];\n\nfunction isOscillationExemptTarget(target: string): boolean {\n if (!target) return false;\n // Target may be a bare path or \"file:/path\" / \"write_file:/path\" etc.\n // Normalize by finding the first \"/\" and comparing from there.\n const slashIdx = target.indexOf('/');\n const pathPart = slashIdx === -1 ? target : target.slice(slashIdx);\n return OSCILLATION_EXEMPT_PREFIXES.some(prefix => pathPart.startsWith(prefix));\n}\n\n/**\n * Evaluate the Safety drive pressure against the sustained-high\n * threshold. Call once per drive tick. Fires kill() when the drive\n * has been > threshold continuously for 10 minutes.\n */\nexport function evaluateSafetyPressure(safetyPressure: number): void {\n const state = load();\n const now = new Date();\n if (safetyPressure > SAFETY_PRESSURE_THRESHOLD) {\n if (!state.safetyHighSince) {\n state.safetyHighSince = now.toISOString();\n save();\n return;\n }\n const elapsed = now.getTime() - new Date(state.safetyHighSince).getTime();\n if (elapsed >= SAFETY_PRESSURE_SUSTAIN_MS && state.status === 'armed') {\n void kill('safety_pressure',\n `Safety drive pressure ${safetyPressure.toFixed(2)} > ${SAFETY_PRESSURE_THRESHOLD} sustained for ${Math.round(elapsed / 60_000)}m`,\n { firedBy: 'soma' });\n }\n } else if (state.safetyHighSince) {\n // Clear the sustained-timer — pressure dropped back below.\n state.safetyHighSince = undefined;\n save();\n }\n}\n\n/**\n * Record a fix-oscillation event (same target fixed twice within 24h).\n * Fires kill when ≥2 oscillations on the SAME target within 24h window.\n *\n * v4.10.0-local fix: Changed from \"3 total events anywhere\" to\n * \"2+ events on same target\" — prevents false positives when editing\n * different files (3 files each edited twice is not oscillation).\n */\nexport function recordFixOscillation(target: string): void {\n // v4.13 ancestor-extraction Sprint B: staging/scratch paths are exempt\n // from fleet-wide kill. They still get logged (below), just don't trigger.\n if (isOscillationExemptTarget(target)) {\n logger.debug(COMPONENT, `Oscillation event on exempt path \"${target.slice(0, 80)}\" — recorded, not counted`);\n return;\n }\n\n const state = load();\n const now = Date.now();\n state.recentOscillations.push({ at: new Date(now).toISOString(), target });\n state.recentOscillations = state.recentOscillations.filter(o =>\n now - new Date(o.at).getTime() < FIX_OSCILLATION_WINDOW_MS,\n );\n save();\n\n // Count oscillations per target\n const targetCounts = new Map<string, number>();\n for (const o of state.recentOscillations) {\n targetCounts.set(o.target, (targetCounts.get(o.target) || 0) + 1);\n }\n\n let maxCount = 0;\n let worstTarget = '';\n for (const [t, count] of targetCounts) {\n if (count > maxCount) {\n maxCount = count;\n worstTarget = t;\n }\n }\n\n // v4.13 ancestor-extraction (Paperclip scoped pause): BEFORE firing the\n // fleet-wide kill, try a scoped per-target pause. If the same target\n // hit >=3× in this window it's suspicious — pause THAT target for 15m\n // (write blocked, everything else continues). The full kill only fires\n // when a single target crosses the higher 5× threshold, which indicates\n // a stuck retry loop rather than occasional repeat edits.\n const SCOPED_PAUSE_THRESHOLD = 3;\n if (maxCount >= SCOPED_PAUSE_THRESHOLD && maxCount < FIX_OSCILLATION_COUNT_THRESHOLD) {\n try {\n // Lazy import to avoid circular deps at module load\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n void (async () => {\n const { pauseTarget, isTargetPaused } = await import('./scopedPause.js');\n if (!isTargetPaused(worstTarget)) {\n pauseTarget(worstTarget, 'fix_oscillation', {\n note: `${maxCount}× events in ${Math.round(FIX_OSCILLATION_WINDOW_MS / 60000)}m`,\n });\n }\n })();\n } catch { /* non-fatal */ }\n }\n\n // Fire fleet-wide kill ONLY when same non-exempt target hits\n // FIX_OSCILLATION_COUNT_THRESHOLD (5×) within FIX_OSCILLATION_WINDOW_MS\n // (1h). At that point it's a genuine stuck loop, not normal operation.\n if (maxCount >= FIX_OSCILLATION_COUNT_THRESHOLD && state.status === 'armed') {\n const totalEvents = state.recentOscillations.length;\n const uniqueTargets = targetCounts.size;\n const windowMin = Math.round(FIX_OSCILLATION_WINDOW_MS / 60000);\n void kill('fix_oscillation',\n `Target \"${worstTarget.slice(0, 60)}\" oscillated ${maxCount}× in ${windowMin}m (${totalEvents} total events across ${uniqueTargets} target(s))`,\n { firedBy: 'fix-oscillation-detector' });\n }\n}\n\n// ── Kill sequence (side effects) ─────────────────────────────────\n\nasync function executeKillSequence(event: KillEvent): Promise<void> {\n const steps: Array<{ name: string; fn: () => Promise<void> | void }> = [\n { name: 'disable-autopilot', fn: disableAutopilot },\n { name: 'pause-active-goals', fn: pauseActiveGoals },\n { name: 'pause-specialists', fn: pauseSpecialists },\n { name: 'abort-in-flight', fn: abortInFlightSessions },\n { name: 'broadcast-sse', fn: () => broadcastKill(event) },\n ];\n for (const step of steps) {\n try {\n await step.fn();\n logger.info(COMPONENT, `kill seq: ${step.name} ✓`);\n } catch (err) {\n logger.warn(COMPONENT, `kill seq: ${step.name} failed: ${(err as Error).message}`);\n }\n }\n}\n\nasync function disableAutopilot(): Promise<void> {\n try {\n // Best-effort: set in-memory flag that the scheduler checks\n const g = globalThis as unknown as { __titan_autopilot_killed?: boolean };\n g.__titan_autopilot_killed = true;\n } catch { /* ok */ }\n}\n\nasync function pauseActiveGoals(): Promise<void> {\n try {\n const { listGoals, updateGoal } = await import('../agent/goals.js');\n let paused = 0;\n for (const g of listGoals()) {\n if (g.status === 'active') {\n try { updateGoal(g.id, { status: 'paused' }); paused++; } catch { /* skip */ }\n }\n }\n logger.info(COMPONENT, `kill: paused ${paused} active goal(s)`);\n } catch (err) {\n logger.warn(COMPONENT, `kill: pauseActiveGoals unavailable: ${(err as Error).message}`);\n }\n}\n\nasync function pauseSpecialists(): Promise<void> {\n const mod = await import('../agent/commandPost.js').catch(() => null);\n if (!mod) return;\n const agents = mod.getRegisteredAgents();\n let paused = 0;\n for (const a of agents) {\n if (a.status !== 'active' && a.status !== 'idle') continue;\n try { mod.updateAgentStatus(a.id, 'paused'); paused++; } catch { /* skip */ }\n }\n logger.info(COMPONENT, `kill: paused ${paused} agent(s)`);\n}\n\nasync function abortInFlightSessions(): Promise<void> {\n // In-flight abort hooks would be registered on globalThis by the\n // agent loop; here we set a flag the loop checks each round.\n const g = globalThis as unknown as { __titan_abort_all?: boolean };\n g.__titan_abort_all = true;\n // Clear after 30s so normal operations can resume when Tony unpauses.\n setTimeout(() => { g.__titan_abort_all = false; }, 30_000).unref?.();\n}\n\nfunction broadcastKill(event: KillEvent): void {\n const g = globalThis as unknown as { __titan_sse_broadcast?: (topic: string, payload: unknown) => void };\n if (typeof g.__titan_sse_broadcast === 'function') {\n try { g.__titan_sse_broadcast('safety:killed', event); } catch { /* ok */ }\n }\n}\n\n/** Test-only cache reset. */\nexport function _resetKillSwitchCacheForTests(): void { cache = null; }\n"],"mappings":"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","names":[]}
|
|
@@ -7,7 +7,7 @@ import { chat } from "../../providers/router.js";
|
|
|
7
7
|
import { loadConfig } from "../../config/config.js";
|
|
8
8
|
import { applyOutputGuardrails } from "../../agent/outputGuardrails.js";
|
|
9
9
|
import { TITAN_HOME } from "../../utils/constants.js";
|
|
10
|
-
import {
|
|
10
|
+
import { mkdirIfNotExists } from "../../utils/helpers.js";
|
|
11
11
|
import logger from "../../utils/logger.js";
|
|
12
12
|
const COMPONENT = "AgentDebate";
|
|
13
13
|
const DEBATES_DIR = join(TITAN_HOME, "debates");
|
|
@@ -294,7 +294,7 @@ function parseJudgeVerdict(raw) {
|
|
|
294
294
|
}
|
|
295
295
|
function persistTranscript(t) {
|
|
296
296
|
try {
|
|
297
|
-
|
|
297
|
+
mkdirIfNotExists(DEBATES_DIR);
|
|
298
298
|
writeFileSync(join(DEBATES_DIR, `${t.id}.json`), JSON.stringify(t, null, 2), "utf-8");
|
|
299
299
|
} catch (err) {
|
|
300
300
|
logger.warn(COMPONENT, `Failed to persist transcript ${t.id}: ${err.message}`);
|