titan-agent 5.4.2 → 5.5.6
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/peerAdvise.js +1 -1
- package/dist/agent/peerAdvise.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/fixOscillation.js +15 -0
- package/dist/safety/fixOscillation.js.map +1 -1
- package/dist/safety/killSwitch.js +2 -2
- package/dist/safety/killSwitch.js.map +1 -1
- package/dist/safety/selfRepair.js +7 -3
- package/dist/safety/selfRepair.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":";AAWA,SAAS,mBAA8E;AACvF,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;AACnC,SAAS,sBAAsB,wBAAwB;AACvD,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AACnB,SAAS,uBAAuB;AAEhC,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,aAAa;AACtB,SAAS,uBAAuB,4BAA4B,sBAAsB;AAClF,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,YAAY;AAGlB,SAAS,iBAAiB,iBAAmC;AACzD,QAAM,WAAmC;AAAA,IACrC,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,IACV,UAAU;AAAA,IACV,KAAK;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,QAAQ;AAAA,EACZ;AACA,gBAAc;AACd,SAAO,MAAM,KAAK,UAAU,KAAK,CAAC,EAC7B,OAAO,UAAQ,SAAS,eAAe,EACvC,KAAK,CAAC,GAAG,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,GAAG;AACjE;AAOA,SAAS,0BAA0B,MAAsB;AACrD,MAAI,UAAU;AAGd,YAAU,QAAQ,QAAQ,8BAA8B,EAAE;AAG1D,YAAU,QAAQ,QAAQ,4BAA4B,EAAE;AAIxD,QAAM,gBAAgB;AACtB,QAAM,aAAa,QAAQ,MAAM,aAAa;AAC9C,MAAI,YAAY,UAAU,UAAa,WAAW,QAAQ,IAAI;AAC1D,cAAU,QAAQ,MAAM,GAAG,WAAW,KAAK;AAAA,EAC/C;AAGA,QAAM,iBAAiB;AACvB,MAAI,eAAe,KAAK,QAAQ,KAAK,CAAC,GAAG;AACrC,UAAM,QAAQ,QAAQ,MAAM,eAAe;AAC3C,UAAM,aAAa,MAAM,OAAO,OAAK;AACjC,YAAM,UAAU,EAAE,KAAK;AACvB,UAAI,CAAC,QAAS,QAAO;AACrB,UAAI,eAAe,KAAK,OAAO,EAAG,QAAO;AACzC,UAAI,2FAA2F,KAAK,OAAO,EAAG,QAAO;AACrH,UAAI,mEAAmE,KAAK,OAAO,EAAG,QAAO;AAC7F,aAAO;AAAA,IACX,CAAC;AACD,QAAI,WAAW,SAAS,GAAG;AACvB,gBAAU,WAAW,KAAK,MAAM;AAAA,IACpC;AAAA,EACJ;AAGA,YAAU,QAAQ,KAAK,EAAE,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AAE1D,SAAO;AACX;AAGA,MAAM,mBAA2C;AAAA,EAC7C,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,cAAc;AAClB;AAGO,SAAS,kBAAkB,MAAsB;AACpD,QAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,iBAAiB,KAAK,KAAK;AACtC;AAGA,MAAM,YAAsC,oBAAI,IAAI;AACpD,IAAI,cAAc;AAElB,SAAS,gBAAsB;AAC3B,MAAI,YAAa;AAEjB,YAAU,IAAI,aAAa,IAAI,kBAAkB,CAAC;AAClD,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,eAAe,IAAI,mBAAmB,CAAC;AAErD,aAAW,UAAU,kBAAkB;AACnC,cAAU,IAAI,OAAO,MAAM,IAAI,qBAAqB,MAAM,CAAC;AAAA,EAC/D;AACA,gBAAc;AAClB;AAGO,SAAS,YAAY,MAAuC;AAC/D,gBAAc;AACd,SAAO,UAAU,IAAI,IAAI;AAC7B;AAGO,SAAS,kBAA4C;AACxD,gBAAc;AACd,SAAO;AACX;AAGA,SAAS,aAAa,SAAyB;AAC3C,QAAM,SAAS,WAAW;AAC1B,QAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,WAAW,QAAQ,OAAO,GAAG;AAC7B,UAAM,WAAW,QAAQ,OAAO;AAChC,WAAO,MAAM,WAAW,UAAU,OAAO,aAAQ,QAAQ,GAAG;AAC5D,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAIO,SAAS,aAAa,SAA2D;AACpF,gBAAc;AAEd,QAAM,WAAW,aAAa,OAAO;AACrC,QAAM,EAAE,UAAU,iBAAiB,MAAM,IAAI,YAAY,aAAa,QAAQ;AAI9E,QAAM,eAAe,kBAAkB,eAAe;AACtD,QAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,MAAI,CAAC,UAAU;AACX,UAAM,IAAI,MAAM,qBAAqB,YAAY,gBAAgB,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EAC9G;AACA,SAAO,EAAE,UAAU,MAAM;AAC7B;AAGO,SAAS,eAAe,SAA0B;AACrD,QAAM,SAAS,WAAW;AAC1B,QAAM,gBAAgB,OAAO,MAAM;AACnC,MAAI,CAAC,iBAAiB,cAAc,WAAW,EAAG,QAAO;AAGzD,QAAM,WAAW,aAAa,OAAO;AAErC,aAAW,WAAW,eAAe;AACjC,QAAI,YAAY,SAAU,QAAO;AAEjC,QAAI,QAAQ,SAAS,IAAI,GAAG;AACxB,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,UAAI,SAAS,WAAW,MAAM,EAAG,QAAO;AAAA,IAC5C;AAAA,EACJ;AACA,SAAO;AACX;AAYA,IAAI,aAAsE;AAC1E,MAAM,kBAAkB;AAOxB,eAAsB,kBAAkB,eAAe,OAAmC;AACtF,gBAAc;AAEd,MAAI,CAAC,gBAAgB,cAAe,KAAK,IAAI,IAAI,WAAW,YAAa,iBAAiB;AACtF,WAAO,WAAW;AAAA,EACtB;AAEA,QAAM,aAAgC,CAAC;AACvC,QAAM,SAAS,MAAM,eAAe;AAEpC,QAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,QAAQ,MAAM;AAC1E,QAAI;AACA,YAAM,SAAS,MAAM,SAAS,WAAW;AACzC,YAAM,SAAS,OAAO,IAAI,MAAM;AAChC,iBAAW,SAAS,QAAQ;AACxB,mBAAW,KAAK;AAAA,UACZ,IAAI,GAAG,IAAI,IAAI,KAAK;AAAA,UACpB,UAAU;AAAA,UACV;AAAA,UACA,aAAa,SAAS;AAAA,UACtB,QAAS,SAAS,YAAY,SAAU,SAAS;AAAA,QACrD,CAAC;AAAA,MACL;AAAA,IACJ,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,6BAA6B,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,IAC1F;AAAA,EACJ,CAAC;AAED,QAAM,QAAQ,IAAI,KAAK;AAEvB,eAAa,EAAE,QAAQ,YAAY,WAAW,KAAK,IAAI,EAAE;AACzD,SAAO,KAAK,WAAW,cAAc,WAAW,MAAM,kBAAkB,UAAU,IAAI,YAAY;AAClG,SAAO;AACX;AAGO,SAAS,kBAA0C;AACtD,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,MAAM,gBAAgB,CAAC;AACzC;AAeA,MAAM,yBAAyB;AAAA,EAC3B,kBAAkB;AAAA;AAAA,EAClB,cAAc;AAAA;AAAA,EACd,kBAAkB;AAAA;AAAA,EAClB,kBAAkB;AAAA;AACtB;AAGA,MAAM,kBAAkB,oBAAI,IAAiC;AAG7D,YAAY,MAAM;AACd,QAAM,MAAM,KAAK,IAAI;AACrB,aAAW,CAAC,MAAM,KAAK,KAAK,iBAAiB;AACzC,QAAI,MAAM,UAAU,YAAY,MAAM,mBAAmB,MAAM,MAAM,kBAAkB,KAAS;AAC5F,sBAAgB,OAAO,IAAI;AAAA,IAC/B;AAAA,EACJ;AACJ,GAAG,GAAO;AAQV,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B,oBAAI,IAAoB;AAG3D,SAAS,wBAAwB,cAA4B;AACzD,6BAA2B,IAAI,cAAc,KAAK,IAAI,CAAC;AAC3D;AAGA,SAAS,sBAAsB,cAA+B;AAC1D,QAAM,gBAAgB,2BAA2B,IAAI,YAAY;AACjE,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,MAAI,WAAW,uBAAuB;AAClC,+BAA2B,OAAO,YAAY;AAC9C,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAKA,SAAS,kBAAkB,cAA2C;AAClE,MAAI,CAAC,gBAAgB,IAAI,YAAY,GAAG;AACpC,oBAAgB,IAAI,cAAc;AAAA,MAC9B,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,WAAW;AAAA,IACf,CAAC;AAAA,EACL;AACA,SAAO,gBAAgB,IAAI,YAAY;AAC3C;AAMA,SAAS,cAAc,cAA4B;AAC/C,QAAM,KAAK,kBAAkB,YAAY;AACzC,KAAG,kBAAkB,KAAK,IAAI;AAE9B,MAAI,GAAG,UAAU,aAAa;AAE1B,OAAG,eAAe,KAAK,IAAI,GAAG,GAAG,eAAe,CAAC;AAEjD,QAAI,GAAG,gBAAgB,GAAG;AACtB,SAAG,QAAQ;AACX,SAAG,YAAY;AACf,SAAG,eAAe;AAClB,aAAO,KAAK,WAAW,oBAAoB,YAAY,2CAA2C;AAAA,IACtG;AAAA,EACJ,WAAW,GAAG,UAAU,UAAU;AAE9B,OAAG,eAAe;AAAA,EACtB;AACJ;AAMA,SAAS,cAAc,cAA4B;AAC/C,QAAM,KAAK,kBAAkB,YAAY;AACzC,QAAM,MAAM,KAAK,IAAI;AACrB,KAAG,kBAAkB;AAGrB,QAAM,cAAc,MAAM,uBAAuB;AACjD,MAAI,GAAG,mBAAmB,GAAG,kBAAkB,aAAa;AAExD,OAAG,eAAe;AAAA,EACtB,OAAO;AACH,OAAG;AAAA,EACP;AAGA,MAAI,GAAG,gBAAgB,uBAAuB,oBAAoB,GAAG,UAAU,UAAU;AACrF,OAAG,QAAQ;AACX,OAAG,YAAY;AACf,WAAO,KAAK,WAAW,oBAAoB,YAAY,yBAAyB,GAAG,YAAY,WAAW;AAAA,EAC9G;AACJ;AAOA,SAAS,WAAW,cAAsB,kBAAkB,OAAgB;AAGxE,MAAI,mBAAmB,sBAAsB,YAAY,GAAG;AACxD,WAAO,MAAM,WAAW,uBAAuB,YAAY,oDAA+C;AAC1G,WAAO;AAAA,EACX;AAEA,QAAM,KAAK,kBAAkB,YAAY;AAEzC,MAAI,GAAG,UAAU,UAAU;AACvB,WAAO;AAAA,EACX;AAEA,MAAI,GAAG,UAAU,QAAQ;AACrB,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,GAAG,aAAc,MAAM,GAAG,aAAc,uBAAuB,cAAc;AAE7E,SAAG,QAAQ;AACX,SAAG,eAAe,uBAAuB;AACzC,aAAO,KAAK,WAAW,oBAAoB,YAAY,8CAA8C;AACrG,aAAO;AAAA,IACX;AACA,WAAO;AAAA,EACX;AAGA,SAAO;AACX;AAKO,SAAS,0BAA6G;AACzH,QAAM,SAA4F,CAAC;AACnG,aAAW,CAAC,cAAc,EAAE,KAAK,iBAAiB;AAC9C,WAAO,YAAY,IAAI;AAAA,MACnB,OAAO,GAAG;AAAA,MACV,cAAc,GAAG;AAAA,MACjB,GAAI,GAAG,cAAc,OAAO,EAAE,WAAW,GAAG,UAAU,IAAI,CAAC;AAAA,IAC/D;AAAA,EACJ;AACA,SAAO;AACX;AAMO,SAAS,2BAAiC;AAC7C,kBAAgB,MAAM;AACtB,sBAAoB;AACxB;AAEO,SAAS,oBAAoB,cAA4B;AAC5D,QAAM,KAAK,gBAAgB,IAAI,YAAY;AAC3C,MAAI,IAAI;AACJ,OAAG,QAAQ;AACX,OAAG,eAAe;AAClB,OAAG,YAAY;AAAA,EACnB;AACJ;AAIA,IAAI,oBAAmG;AAGhG,SAAS,mBAAkG;AAE9G,MAAI,qBAAsB,KAAK,IAAI,IAAI,kBAAkB,YAAa,KAAS;AAC3E,wBAAoB;AAAA,EACxB;AACA,SAAO;AACX;AAGA,MAAM,eAAe;AAAA,EACjB,YAAY;AAAA;AAAA,EACZ,gBAAgB;AAAA;AAAA,EAChB,YAAY;AAAA;AAAA,EACZ,mBAAmB;AAAA,EACnB,QAAQ;AACZ;AAQA,IAAI,iBAAiB;AAuBrB,SAAS,sBAAsB,SAAyB;AACpD,QAAM,mBAAmB,aAAa,iBAAiB,KAAK,IAAI,aAAa,mBAAmB,OAAO;AACvG,QAAM,cAAc,KAAK,IAAI,kBAAkB,aAAa,UAAU;AAEtE,MAAI,CAAC,aAAa,OAAQ,QAAO;AAGjC,mBAAkB,iBAAiB,MAAO;AAC1C,QAAM,QAAQ,KAAK,IAAI,IAAK,iBAAiB,gBAAiB;AAE9D,MAAI,IAAI,QAAQ;AAChB,OAAK,KAAK;AAAI,SAAO;AACrB,OAAK,MAAM;AACX,OAAK,KAAK;AAAG,SAAO;AACpB,QAAM,UAAU,MAAM,KAAK;AAE3B,QAAM,cAAc;AACpB,QAAM,SAAS,SAAS,cAAc;AACtC,SAAO,cAAc;AACzB;AAGA,SAAS,gBAAgB,QAAsC;AAC3D,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,UAAU,SAAS,QAAQ,EAAE;AACnC,MAAI,CAAC,MAAM,OAAO,GAAG;AACjB,WAAO,KAAK,IAAI,UAAU,KAAM,aAAa,UAAU;AAAA,EAC3D;AAGA,QAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,MAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,GAAG;AACxB,UAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,IAAI;AACxC,WAAO,KAAK,IAAI,KAAM,KAAK,IAAI,OAAO,aAAa,UAAU,CAAC;AAAA,EAClE;AAEA,SAAO;AACX;AAKA,SAAS,iBAAiB,OAAyB;AAC/C,SAAO,sBAAsB,KAAK,EAAE;AACxC;AAKA,SAAS,eAAe,OAAoC;AACxD,SAAO,sBAAsB,KAAK,EAAE;AACxC;AAGA,eAAe,iBACX,SACA,gBACA,eAC4B;AAC5B,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,QAAM,aAAa,OAAO,MAAM,sBAAsB;AACtD,MAAI,WAAW;AAEf,aAAW,mBAAmB,OAAO;AACjC,QAAI,YAAY,WAAY;AAC5B,QAAI,oBAAoB,eAAgB;AAExC;AACA,QAAI;AACA,YAAM,EAAE,UAAU,YAAY,OAAO,QAAQ,IAAI,aAAa,eAAe;AAC7E,YAAM,iBAAiB,WAAW;AAGlC,UAAI,CAAC,WAAW,gBAAgB,IAAI,GAAG;AACnC,cAAM,KAAK,kBAAkB,cAAc;AAC3C,eAAO,KAAK,WAAW,qBAAqB,eAAe,iCAA4B,GAAG,YAAY,YAAY;AAClH;AAAA,MACJ;AAEA,aAAO,KAAK,WAAW,SAAS,cAAc,YAAY,cAAc,OAAO,sBAAsB,eAAe,EAAE;AACtH,YAAM,SAAS,MAAM,WAAW,KAAK,EAAE,GAAG,SAAS,OAAO,QAAQ,CAAC;AAGnE,oBAAc,cAAc;AAE5B,0BAAoB;AAAA,QAChB,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ,cAAc;AAAA,QACtB,WAAW,KAAK,IAAI;AAAA,MACxB;AACA,aAAO;AAAA,IACX,SAAS,UAAU;AAEf,UAAI;AACA,cAAM,EAAE,UAAU,WAAW,IAAI,aAAa,eAAe;AAC7D,sBAAc,WAAW,IAAI;AAAA,MACjC,QAAQ;AAAA,MAER;AACA,aAAO,KAAK,WAAW,kBAAkB,eAAe,iBAAkB,SAAmB,OAAO,EAAE;AACtG;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAkBA,eAAe,uBACX,SACA,gBACA,eAC+C;AAC/C,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,QAAM,aAAa,OAAO,MAAM,sBAAsB;AACtD,MAAI,WAAW;AAEf,aAAW,mBAAmB,OAAO;AACjC,QAAI,YAAY,WAAY;AAC5B,QAAI,oBAAoB,eAAgB;AAExC;AACA,QAAI;AACJ,QAAI;AAEJ,QAAI;AACA,YAAM,EAAE,UAAU,YAAY,OAAO,QAAQ,IAAI,aAAa,eAAe;AAC7E,uBAAiB,WAAW;AAG5B,UAAI,CAAC,WAAW,gBAAgB,IAAI,GAAG;AACnC,cAAM,KAAK,kBAAkB,cAAc;AAC3C,eAAO,KAAK,WAAW,4BAA4B,eAAe,iCAA4B,GAAG,YAAY,YAAY;AACzH;AAAA,MACJ;AAEA,aAAO,KAAK,WAAW,gBAAgB,cAAc,YAAY,cAAc,OAAO,sBAAsB,eAAe,EAAE;AAC7H,YAAM,WAAW,WAAW,EAAE,GAAG,SAAS,OAAO,QAAQ,CAAC;AAAA,IAC9D,SAAS,UAAU;AAEf,UAAI;AACA,cAAM,EAAE,UAAU,WAAW,IAAI,aAAa,eAAe;AAC7D,sBAAc,WAAW,IAAI;AAAA,MACjC,QAAQ;AAAA,MAER;AACA,aAAO,KAAK,WAAW,yBAAyB,eAAe,kBAAmB,SAAmB,OAAO,EAAE;AAC9G;AAAA,IACJ;AAEA,wBAAoB;AAAA,MAChB,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,QAAQ,cAAc;AAAA,MACtB,WAAW,KAAK,IAAI;AAAA,IACxB;AAEA,WAAO,wBAAwB,KAAK,cAAc;AAAA,EACtD;AACA,SAAO;AACX;AASA,gBAAgB,wBACZ,OACA,cAC+B;AAC/B,MAAI,WAAW;AACf,MAAI;AACA,qBAAiB,SAAS,OAAO;AAC7B,UAAI,MAAM,SAAS,SAAS;AACxB,YAAI,CAAC,UAAU;AAAE,wBAAc,YAAY;AAAG,qBAAW;AAAA,QAAM;AAAA,MACnE;AACA,YAAM;AAAA,IACV;AACA,QAAI,CAAC,SAAU,eAAc,YAAY;AAAA,EAC7C,SAAS,UAAU;AACf,QAAI,CAAC,UAAU;AAAE,oBAAc,YAAY;AAAG,iBAAW;AAAA,IAAM;AAC/D,UAAM;AAAA,EACV;AACJ;AAGA,eAAe,SAAS,MAAgB,SAAiB,SAAwC;AAC7F,QAAM,YAAY,YAAY,CAAC,EAAE,SAAS,KAAK;AAC/C,QAAM,SAAS,WAAW;AAC1B,QAAM,YAAY,OAAO,MAAM,iBAAiB;AAChD,SAAO,KAAK,WAAW,YAAY,OAAO,kBAAkB,KAAK,QAAQ,KAAK,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;AAC3G,QAAM,SAAS,MAAM,gBAAgB,KAAK,QAAQ,WAAW,SAAS,SAAS,SAAS;AACxF,MAAI,OAAO,OAAO;AACd,UAAM,IAAI,MAAM,oBAAoB,OAAO,KAAK,EAAE;AAAA,EACtD;AAEA,GAAC,YAAY;AACT,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,oBAAgB,SAAS,QAAQ,IAAI;AAAA,EACzC,GAAG,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACnB,SAAO;AACX;AAKA,SAAS,2BAA2B,OAAc,cAAsB,OAAe,SAAyB;AAC5G,QAAM,SAAS,eAAe,KAAK;AACnC,QAAM,aAAa,SAAS,SAAS,MAAM,OAAO;AAElD,SAAO;AAAA,IACH,YAAY,YAAY,IAAI,KAAK;AAAA,IACjC,aAAa,MAAM;AAAA,IACnB,UAAU,IAAI,YAAY,UAAU,CAAC,MAAM;AAAA,EAC/C,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC/B;AAMA,eAAsB,KAAK,SAA6C;AACpE,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,EAAE,UAAU,MAAM,IAAI,aAAa,OAAO;AAChD,QAAM,eAAe,SAAS;AAE9B,SAAO,KAAK,WAAW,cAAc,SAAS,WAAW,YAAY,KAAK,GAAG;AAG7E,QAAM,mBAA8F,CAAC;AAGrG,MAAI,CAAC,WAAW,YAAY,GAAG;AAC3B,UAAM,KAAK,kBAAkB,YAAY;AACzC,UAAM,WAAW,4BAA4B,YAAY,IAAI,KAAK,KAAK,GAAG,YAAY,uBAClF,GAAG,YAAY,KAAK,OAAO,uBAAuB,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAc,GAAI,IAAI,SAC5G;AACA,WAAO,KAAK,WAAW,QAAQ;AAC/B,UAAM,gBAAgB,IAAI,MAAM,QAAQ;AACxC,WAAO,OAAO,eAAe,EAAE,QAAQ,KAAK,UAAU,cAAc,MAAM,CAAC;AAC3E,UAAM;AAAA,EACV;AAEA,MAAI,YAA0B;AAC9B,QAAM,aAAa,aAAa;AAOhC,MAAI,qBAAqB;AACzB,MAAI,mBAAmB;AAMvB,MAAI;AACA,UAAM,UAAU,cAAc,YAAY;AAC1C,QAAI,SAAS;AACT,aAAO,KAAK,WAAW,oCAAoC,YAAY,KAAK,QAAQ,MAAM,mBAAc,KAAK,MAAM,QAAQ,SAAS,CAAC,IAAI;AACzI,YAAM,MAAM,QAAQ,SAAS;AAAA,IACjC;AAAA,EACJ,QAAQ;AAAA,EAAsC;AAG9C,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACpD,QAAI;AACA,YAAM,SAAS,MAAM,SAAS,KAAK,EAAE,GAAG,SAAS,MAAM,CAAC;AAGxD,UAAI,OAAO,SAAS;AAChB,eAAO,UAAU,0BAA0B,OAAO,OAAO;AAAA,MAC7D;AAGA,oBAAc,YAAY;AAC1B,0BAAoB;AAGpB,UAAI,UAAU,GAAG;AACb,eAAO,KAAK,WAAW,GAAG,SAAS,WAAW,IAAI,KAAK,oBAAoB,OAAO,mBAAmB;AAAA,MACzG;AAGA,OAAC,YAAY;AACT,cAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,wBAAgB,OAAO,cAAc,IAAI;AAAA,MAC7C,GAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAEnB,aAAO;AAAA,IACX,SAAS,OAAO;AACZ,kBAAY;AAGZ,YAAM,aAAa,sBAAsB,KAAK;AAG9C,UAAI,2BAA2B,UAAU,GAAG;AACxC,sBAAc,YAAY;AAAA,MAC9B;AAOA,UAAI,QAAQ,YAAY;AACpB,cAAMA,YAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AACxF,cAAM,kBAAkB,IAAI;AAAA,UACxB,gBAAgB,YAAY,IAAI,KAAK,mCAAmCA,SAAQ;AAAA,QACpF;AACA,eAAO,OAAO,iBAAiB;AAAA,UAC3B,QAAQ,WAAW;AAAA,UACnB,UAAU;AAAA,UACV;AAAA,UACA,OAAO;AAAA,UACP,gBAAgB,WAAW;AAAA,UAC3B,YAAY;AAAA,QAChB,CAAC;AACD,cAAM;AAAA,MACV;AAGA,UAAI,WAAW,WAAW,eAAe,YAAY;AACjD,gCAAwB,YAAY;AAAA,MACxC;AAGA,UAAI,WAAW,wBAAwB;AACnC,cAAM,OAAO,gBAAgB,YAAY;AACzC,YAAI,MAAM;AAEN,gBAAM,SAAS,KAAK,OAAO;AAC3B,gBAAM,WAAW,OAAO,KAAK,OAAK,EAAE,SAAS;AAC7C,cAAI,UAAU;AACV,iBAAK,QAAQ,SAAS,MAAM,WAAW,cAAc,GAAK;AAAA,UAC9D;AAAA,QACJ;AAAA,MACJ;AAQA,UAAI,WAAW,kBAAkB,CAAC,sBAAsB,MAAM,QAAQ,QAAQ,QAAQ,GAAG;AACrF,6BAAqB;AACrB,cAAM,cAAc,QAAQ,SAAS;AAIrC,cAAM,gBAAgB;AACtB,YAAI;AACA,gBAAM,YAAY,kBAAkB,QAAQ,UAAU,aAAa;AACnE,cAAI,UAAU,SAAS,KAAK,UAAU,UAAU,aAAa;AACzD,sBAAU,EAAE,GAAG,SAAS,UAAU,UAAU;AAC5C,mBAAO;AAAA,cACH;AAAA,cACA,YAAY,WAAW,MAAM,6BAAwB,WAAW,SAAI,UAAU,MAAM,mBAAmB,YAAY,IAAI,KAAK;AAAA,YAChI;AAEA;AAAA,UACJ;AACA,iBAAO,KAAK,WAAW,kFAA6E;AAAA,QACxG,SAAS,SAAS;AACd,iBAAO,KAAK,WAAW,gCAAiC,QAAkB,OAAO,yBAAoB;AAAA,QACzG;AAAA,MACJ;AAMA,UAAI,WAAW,WAAW,eAAe,0BAA0B,CAAC,kBAAkB;AAClF,2BAAmB;AACnB,cAAM,eAAe,QAAQ,kBAAkB,EAAE,GAAG,QAAQ,gBAAgB,IAAI,CAAC;AAEjF,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,kBAAU,EAAE,GAAG,SAAS,iBAAiB,aAAa;AACtD,eAAO,KAAK,WAAW,4EAAuE,YAAY,IAAI,KAAK,EAAE;AACrH;AAAA,MACJ;AAEA,YAAM,WAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AAGxF,UAAI,WAAW,aAAa,UAAU,YAAY;AAE9C,YAAI,eAAe,KAAK,IAAI,WAAW,YAAY,sBAAsB,OAAO,CAAC;AAQjF,cAAM,eAAe;AACrB,YAAI,OAAO,aAAa,iBAAiB,YAAY,aAAa,eAAe,GAAG;AAChF,yBAAe,aAAa;AAC5B,iBAAO,KAAK,WAAW,uCAAuC,KAAK,MAAM,eAAe,GAAI,CAAC,GAAG;AAAA,QACpG,OAAO;AAEH,gBAAM,aAAa,aAAa,SAAS,MAAM,aAAa;AAC5D,cAAI,YAAY;AACZ,kBAAM,SAAS,gBAAgB,UAAU;AACzC,gBAAI,WAAW,MAAM;AACjB,6BAAe;AACf,qBAAO,KAAK,WAAW,gDAAgD,KAAK,MAAM,eAAe,GAAI,CAAC,GAAG;AAAA,YAC7G;AAAA,UACJ;AAAA,QACJ;AAEA,eAAO,KAAK,WAAW,GAAG,QAAQ,KAAK,WAAW,MAAM,wBAAmB,KAAK,MAAM,YAAY,CAAC,IAAI;AACvG,cAAM,MAAM,YAAY;AACxB;AAAA,MACJ;AAGA,UAAI,CAAC,WAAW,WAAW;AACvB,eAAO,MAAM,WAAW,GAAG,QAAQ,0BAAqB,WAAW,MAAM,MAAM,WAAW,aAAa,QAAQ,WAAW,UAAU,KAAK,eAAe,GAAG;AAAA,MAC/J,OAAO;AACH,eAAO,MAAM,WAAW,GAAG,QAAQ,wBAAmB,UAAU,eAAe,WAAW,MAAM,GAAG;AAAA,MACvG;AAGA,UAAI,WAAW,aAAa,WAAW,gBAAgB;AACnD,cAAM,cAAc,MAAM,iBAAiB,SAAS,SAAS,KAAc;AAC3E,YAAI,aAAa;AACb,iBAAO,KAAK,WAAW,iCAAiC,YAAY,IAAI,KAAK,aAAa,WAAW,MAAM,GAAG;AAC9G,iBAAO;AAAA,QACX;AAAA,MACJ;AAGA,YAAM,SAAS,WAAW;AAC1B,UAAI,OAAO,MAAM,SAAS;AACtB,cAAM,OAAO,gBAAgB,OAAO;AACpC,YAAI,MAAM;AACN,cAAI;AACA,kBAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,IACxC,QAAQ,SAAS,IAAI,OAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAC7C,QAA+C,WAAqB;AAC3E,mBAAO,MAAM,SAAS,MAAM,SAAS,OAAO;AAAA,UAChD,SAAS,SAAS;AACd,mBAAO,KAAK,WAAW,wBAAyB,QAAkB,OAAO,EAAE;AAAA,UAC/E;AAAA,QACJ;AAAA,MACJ;AAGA,UAAI,YAAY,GAAG;AACf,cAAM,gBAAgB,iBAAiB,YAAY;AACnD,mBAAW,gBAAgB,eAAe;AACtC,cAAI,iBAAiB,aAAc;AAGnC,cAAI,CAAC,WAAW,cAAc,IAAI,GAAG;AACjC,mBAAO,MAAM,WAAW,qBAAqB,YAAY,8BAAyB;AAClF;AAAA,UACJ;AAEA,gBAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,cAAI,CAAC,SAAU;AAEf,cAAI,cAAc;AAClB,cAAI;AACA,kBAAM,UAAU,MAAM,SAAS,YAAY;AAC3C,gBAAI,CAAC,QAAS;AAEd,kBAAM,SAAS,MAAM,SAAS,WAAW;AACzC,gBAAI,OAAO,WAAW,EAAG;AAGzB,kBAAM,iBAAiB,MAAM,MAAM,GAAG,EAAE,CAAC;AACzC,0BAAc,OAAO,KAAK,OAAK,EAAE,WAAW,cAAc,CAAC,KAAK,OAAO,CAAC;AAExE,mBAAO,KAAK,WAAW,qBAAqB,YAAY,IAAI,KAAK,WAAM,YAAY,IAAI,WAAW,EAAE;AACpG,kBAAM,SAAS,MAAM,SAAS,KAAK,EAAE,GAAG,SAAS,OAAO,YAAY,CAAC;AACrE,0BAAc,YAAY;AAE1B,aAAC,YAAY;AACT,oBAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,8BAAgB,aAAa,cAAc,IAAI;AAAA,YACnD,GAAG,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AACnB,mBAAO;AAAA,UACX,SAAS,aAAa;AAClB,0BAAc,YAAY;AAE1B,6BAAiB,KAAK;AAAA,cAClB,UAAU;AAAA,cACV,OAAO;AAAA,cACP,OAAQ,YAAsB;AAAA,cAC9B,QAAQ,sBAAsB,WAAW,EAAE;AAAA,YAC/C,CAAC;AACD,mBAAO,KAAK,WAAW,YAAY,YAAY,iBAAkB,YAAsB,OAAO,EAAE;AAChG;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAGA,uBAAiB,QAAQ;AAAA,QACrB,UAAU;AAAA,QACV;AAAA,QACA,OAAQ,MAAgB;AAAA,QACxB,QAAQ,WAAW;AAAA,MACvB,CAAC;AAGD,OAAC,YAAY;AACT,cAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,wBAAgB,OAAO,cAAc,KAAK;AAAA,MAC9C,GAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAGnB,YAAM,iBAAiB,iBAAiB,SAAS,IAC3C,YAAY,iBAAiB,MAAM,eAAe,iBAAiB,IAAI,OAAK,GAAG,EAAE,QAAQ,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,EAAE,KAAK,IAAI,CAAC,KAChI;AACN,YAAM,aAAa,IAAI,MAAM,yBAAyB,QAAQ,GAAG,cAAc,EAAE;AACjF,aAAO,OAAO,YAAY;AAAA,QACtB,QAAQ,WAAW;AAAA,QACnB,UAAU;AAAA,QACV;AAAA,QACA,OAAO;AAAA,QACP,gBAAgB,WAAW;AAAA;AAAA,QAE3B;AAAA,MACJ,CAAC;AACD,YAAM;AAAA,IACV;AAAA,EACJ;AAGA,QAAM,aAAa,IAAI,MAAM,YAAY,YAAY,IAAI,KAAK,2BAA2B;AAC7F;AAKA,gBAAuB,WAAW,SAAuD;AACrF,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,EAAE,UAAU,MAAM,IAAI,aAAa,OAAO;AAChD,QAAM,eAAe,SAAS;AAE9B,SAAO,KAAK,WAAW,iBAAiB,SAAS,WAAW,YAAY,KAAK,GAAG;AAGhF,MAAI,CAAC,WAAW,YAAY,GAAG;AAC3B,UAAM,KAAK,kBAAkB,YAAY;AACzC,UAAM;AAAA,MACF,MAAM;AAAA,MACN,OAAO,kCAAkC,YAAY,IAAI,KAAK,KAAK,GAAG,YAAY,yBAC9E,KAAK,OAAO,uBAAuB,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAe,GAAI,CAC1F;AAAA,IACJ;AACA;AAAA,EACJ;AAEA,MAAI,YAA0B;AAC9B,QAAM,aAAa,aAAa;AAMhC,MAAI,yBAAyB;AAC7B,MAAI,4BAA4B;AAEhC,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACpD,QAAI;AAGA,UAAI,kBAAkB;AACtB,uBAAiB,SAAS,SAAS,WAAW,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG;AAClE,YAAI,CAAC,mBAAmB,MAAM,SAAS,WAAW,YAAY,GAAG;AAC7D,wBAAc,YAAY;AAC1B,4BAAkB;AAAA,QACtB;AACA,4BAAoB;AACpB,cAAM;AAAA,MACV;AAGA,UAAI,UAAU,GAAG;AACb,eAAO,KAAK,WAAW,GAAG,SAAS,WAAW,IAAI,KAAK,2BAA2B,OAAO,mBAAmB;AAAA,MAChH;AACA;AAAA,IACJ,SAAS,OAAO;AACZ,kBAAY;AAGZ,YAAM,aAAa,sBAAsB,KAAK;AAC9C,UAAI,2BAA2B,UAAU,GAAG;AACxC,sBAAc,YAAY;AAAA,MAC9B;AAEA,YAAM,WAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AAGxF,UAAI,WAAW,aAAa,UAAU,YAAY;AAC9C,cAAM,eAAe,KAAK,IAAI,WAAW,YAAY,sBAAsB,OAAO,CAAC;AACnF,eAAO,KAAK,WAAW,GAAG,QAAQ,KAAK,WAAW,MAAM,+BAA0B,KAAK,MAAM,YAAY,CAAC,IAAI;AAM9G,cAAM;AAAA,UACF,MAAM;AAAA,UACN,SAAS,UAAU;AAAA,UACnB;AAAA,UACA,QAAQ,WAAW;AAAA,UACnB,UAAU;AAAA,UACV;AAAA,UACA,SAAS,KAAK,MAAM,YAAY;AAAA,QACpC;AAEA,cAAM,MAAM,YAAY;AACxB;AAAA,MACJ;AAGA,UAAI,CAAC,WAAW,WAAW;AACvB,eAAO,MAAM,WAAW,GAAG,QAAQ,oCAA+B,WAAW,MAAM,GAAG;AAAA,MAC1F,OAAO;AACH,eAAO,MAAM,WAAW,GAAG,QAAQ,2CAAsC,WAAW,MAAM,GAAG;AAAA,MACjG;AAGA,UAAI,CAAC,2BAA2B,WAAW,aAAa,WAAW,iBAAiB;AAChF,iCAAyB;AACzB,cAAM,cAAc,MAAM,uBAAuB,SAAS,SAAS,KAAc;AACjF,YAAI,aAAa;AACb,gBAAM;AAAA,YACF,MAAM;AAAA,YACN,kBAAkB;AAAA,YAClB,eAAe;AAAA,YACf,OAAQ,MAAgB;AAAA,UAC5B;AACA,iBAAO;AACP;AAAA,QACJ;AAAA,MACJ;AAGA,YAAM,SAAS,WAAW;AAC1B,UAAI,OAAO,MAAM,SAAS;AACtB,cAAM,OAAO,gBAAgB,OAAO;AACpC,YAAI,MAAM;AACN,cAAI;AACA,kBAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,IACxC,QAAQ,SAAS,IAAI,OAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAC7C,QAA+C,WAAqB;AAC3E,kBAAM,SAAS,MAAM,SAAS,MAAM,SAAS,OAAO;AACpD,kBAAM,EAAE,MAAM,QAAiB,SAAS,OAAO,QAAQ;AACvD,kBAAM,EAAE,MAAM,OAAgB;AAC9B;AAAA,UACJ,SAAS,SAAS;AACd,mBAAO,KAAK,WAAW,+BAAgC,QAAkB,OAAO,EAAE;AAAA,UACtF;AAAA,QACJ;AAAA,MACJ;AAMA,UAAI,CAAC,2BAA2B;AAC5B,oCAA4B;AAC5B,cAAM,gBAAgB,iBAAiB,YAAY;AACnD,YAAI,aAAa;AAEjB,mBAAW,gBAAgB,eAAe;AACtC,cAAI,iBAAiB,aAAc;AAEnC,cAAI,CAAC,WAAW,cAAc,IAAI,GAAG;AACjC,mBAAO,MAAM,WAAW,4BAA4B,YAAY,8BAAyB;AACzF;AAAA,UACJ;AAEA,gBAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,cAAI,CAAC,SAAU;AAEf,cAAI;AACA,kBAAM,UAAU,MAAM,SAAS,YAAY;AAC3C,gBAAI,CAAC,QAAS;AAEd,kBAAM,SAAS,MAAM,SAAS,WAAW;AACzC,gBAAI,OAAO,WAAW,EAAG;AAEzB,kBAAM,iBAAiB,MAAM,MAAM,GAAG,EAAE,CAAC;AACzC,kBAAM,YAAY,OAAO,KAAK,OAAK,EAAE,WAAW,cAAc,CAAC,KAAK,OAAO,CAAC;AAE5E,mBAAO,KAAK,WAAW,4BAA4B,YAAY,IAAI,KAAK,WAAM,YAAY,IAAI,SAAS,EAAE;AAGzG,kBAAM;AAAA,cACF,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,eAAe;AAAA,cACf,OAAO;AAAA,YACX;AAKA,gBAAI,WAAW;AACf,gBAAI;AACA,+BAAiB,SAAS,SAAS,WAAW,EAAE,GAAG,SAAS,OAAO,UAAU,CAAC,GAAG;AAC7E,oBAAI,MAAM,SAAS,WAAW,CAAC,UAAU;AACrC,gCAAc,YAAY;AAC1B,6BAAW;AAAA,gBACf;AACA,sBAAM;AAAA,cACV;AACA,kBAAI,CAAC,SAAU,eAAc,YAAY;AAAA,YAC7C,SAAS,UAAU;AACf,kBAAI,CAAC,SAAU,eAAc,YAAY;AACzC,oBAAM;AAAA,YACV;AACA,yBAAa;AACb;AAAA,UACJ,SAAS,aAAa;AAClB,0BAAc,YAAY;AAC1B,mBAAO,KAAK,WAAW,mBAAmB,YAAY,iBAAkB,YAAsB,OAAO,EAAE;AACvG;AAAA,UACJ;AAAA,QACJ;AAEA,YAAI,WAAY;AAAA,MACpB;AAGA,YAAM,EAAE,MAAM,SAAS,OAAO,mCAAmC,QAAQ,GAAG;AAC5E;AAAA,IACJ;AAAA,EACJ;AAGA,QAAM,EAAE,MAAM,SAAS,OAAO,WAAW,WAAW,qCAAqC;AAC7F;AAGA,eAAsB,iBAAmD;AACrE,gBAAc;AACd,QAAM,UAAU,MAAM,KAAK,UAAU,QAAQ,CAAC;AAC9C,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC1B,QAAQ,IAAI,CAAC,CAAC,EAAE,QAAQ,MAAM,SAAS,YAAY,CAAC;AAAA,EACxD;AACA,QAAM,UAAmC,CAAC;AAC1C,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACrC,UAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;AACxB,UAAM,UAAU,QAAQ,CAAC;AACzB,YAAQ,IAAI,IAAI,QAAQ,WAAW,cAAc,QAAQ,QAAQ;AAAA,EACrE;AACA,SAAO;AACX;","names":["errorMsg"]}
|
|
@@ -63,9 +63,24 @@ function normalizeTarget(kind, raw) {
|
|
|
63
63
|
return t;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
const TRANSIENT_FILE_PATTERNS = [
|
|
67
|
+
/^\/tmp\//,
|
|
68
|
+
/^\/var\/tmp\//,
|
|
69
|
+
/^\/private\/tmp\//,
|
|
70
|
+
/^\/run\/user\//,
|
|
71
|
+
/\.tmp(\.|\b)/,
|
|
72
|
+
/~$/
|
|
73
|
+
];
|
|
74
|
+
function isTransientPath(kind, normalized) {
|
|
75
|
+
if (kind !== "file") return false;
|
|
76
|
+
return TRANSIENT_FILE_PATTERNS.some((re) => re.test(normalized));
|
|
77
|
+
}
|
|
66
78
|
function recordFixEvent(opts) {
|
|
67
79
|
const now = /* @__PURE__ */ new Date();
|
|
68
80
|
const normalized = normalizeTarget(opts.kind, opts.target);
|
|
81
|
+
if (isTransientPath(opts.kind, normalized)) {
|
|
82
|
+
return { oscillation: false, priorCount: 0 };
|
|
83
|
+
}
|
|
69
84
|
const events = loadRecentEvents();
|
|
70
85
|
const cutoff = now.getTime() - OSCILLATION_WINDOW_MS;
|
|
71
86
|
const priors = events.filter(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safety/fixOscillation.ts"],"sourcesContent":["/**\n * TITAN — Fix Oscillation Detector (v4.9.0+, local hard-takeoff)\n *\n * \"The fix that made it worse\" detector.\n *\n * Every time TITAN modifies a target (file, goal, drive setpoint,\n * prompt), we record a `fixEvent`. If the SAME target gets fixed\n * TWICE within 24h, it's an oscillation — likely either:\n * - The first fix didn't actually work, TITAN is redoing it\n * - The first fix broke something adjacent, TITAN is now patching\n * the breakage\n * - The fix itself keeps oscillating because two contradictory\n * proposals disagree\n *\n * Each oscillation report goes to the kill switch, which fires when\n * ≥3 oscillations hit in a 24h window.\n *\n * This module is intentionally agent-agnostic: it records events\n * describing WHAT was changed, not WHO changed it. Target strings\n * are normalized so different call sites for the same fix collapse\n * correctly.\n *\n * Storage: <TITAN_HOME>/fix-events.jsonl (append-only, bounded).\n */\nimport { existsSync, readFileSync, appendFileSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport logger from '../utils/logger.js';\nimport { recordFixOscillation } from './killSwitch.js';\n\nconst COMPONENT = 'FixOscillation';\nconst EVENTS_PATH = join(TITAN_HOME, 'fix-events.jsonl');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type FixTargetKind =\n | 'file'\n | 'goal'\n | 'drive'\n | 'prompt'\n | 'config'\n | 'skill'\n | 'approval'\n | 'other';\n\nexport interface FixEvent {\n /** Normalized target identifier. Same target = same string. */\n target: string;\n kind: FixTargetKind;\n /** Short description of what was changed — shows up in audit. */\n detail: string;\n /** Session / agent that made the change. Empty string for system ops. */\n by: string;\n at: string;\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nconst OSCILLATION_WINDOW_MS = 24 * 60 * 60 * 1000; // 24h\nconst EVENTS_FILE_MAX_LINES = 5000;\n\nfunction ensureDir(): void {\n try { mkdirSync(dirname(EVENTS_PATH), { recursive: true }); } catch { /* ok */ }\n}\n\n/**\n * Load recent events. We keep the file bounded — beyond MAX_LINES we\n * rewrite with a trailing tail. Cheap O(lines) per boot; acceptable\n * since bounded.\n */\nfunction loadRecentEvents(): FixEvent[] {\n if (!existsSync(EVENTS_PATH)) return [];\n try {\n const raw = readFileSync(EVENTS_PATH, 'utf-8');\n const lines = raw.split('\\n').filter(Boolean);\n const events: FixEvent[] = [];\n for (const line of lines) {\n try {\n const ev = JSON.parse(line) as FixEvent;\n if (ev.target && ev.at) events.push(ev);\n } catch { /* malformed line, skip */ }\n }\n return events;\n } catch (err) {\n logger.warn(COMPONENT, `fix-events.jsonl parse failed: ${(err as Error).message}`);\n return [];\n }\n}\n\nfunction append(event: FixEvent): void {\n ensureDir();\n appendFileSync(EVENTS_PATH, JSON.stringify(event) + '\\n', 'utf-8');\n // Bounded file: rewrite with trailing tail if exceeded.\n try {\n const lines = readFileSync(EVENTS_PATH, 'utf-8').split('\\n');\n if (lines.length > EVENTS_FILE_MAX_LINES) {\n writeFileSync(EVENTS_PATH, lines.slice(-EVENTS_FILE_MAX_LINES).join('\\n'), 'utf-8');\n }\n } catch { /* best-effort */ }\n}\n\n// ── Normalization ────────────────────────────────────────────────\n\n/**\n * Collapse variations of the same target into one identifier. E.g. a\n * file path with a trailing slash, a goal id in different case, a\n * drive with/without the \"soma:\" prefix — all normalize to one key.\n */\nexport function normalizeTarget(kind: FixTargetKind, raw: string): string {\n const t = String(raw ?? '').trim();\n switch (kind) {\n case 'file':\n // Resolve symlinks we can't from here, but collapse to absolute\n // path + strip trailing slash + lowercase on mac.\n return t.replace(/\\/+$/, '').toLowerCase();\n case 'goal':\n return t.toLowerCase();\n case 'drive':\n return t.replace(/^soma:/i, '').toLowerCase();\n case 'prompt':\n case 'config':\n case 'skill':\n return t.toLowerCase();\n case 'approval':\n return t;\n default:\n return t;\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────\n\n/**\n * Record a fix event. If this is the SECOND (or later) fix on the same\n * target within the oscillation window, the kill switch is notified via\n * `recordFixOscillation`. The kill switch itself decides when enough\n * oscillation events have accumulated to fire.\n *\n * Returns `{ oscillation: boolean, priorCount }`:\n * - oscillation=true when this event caused an oscillation to be\n * reported\n * - priorCount = number of prior fix events on the same target\n * within the window\n */\nexport function recordFixEvent(opts: {\n target: string;\n kind: FixTargetKind;\n detail: string;\n by?: string;\n}): { oscillation: boolean; priorCount: number } {\n const now = new Date();\n const normalized = normalizeTarget(opts.kind, opts.target);\n const events = loadRecentEvents();\n const cutoff = now.getTime() - OSCILLATION_WINDOW_MS;\n const priors = events.filter(e =>\n e.kind === opts.kind &&\n normalizeTarget(e.kind, e.target) === normalized &&\n new Date(e.at).getTime() >= cutoff,\n );\n\n const event: FixEvent = {\n target: normalized,\n kind: opts.kind,\n detail: opts.detail.slice(0, 400),\n by: opts.by ?? '',\n at: now.toISOString(),\n };\n append(event);\n\n if (priors.length >= 1) {\n // Same target fixed at least once in the window — this is an\n // oscillation. Notify kill switch, which counts toward its\n // 3-in-24h threshold.\n logger.warn(COMPONENT, `Oscillation on ${opts.kind} \"${normalized.slice(0, 80)}\" (${priors.length + 1}× in 24h): ${opts.detail.slice(0, 100)}`);\n recordFixOscillation(`${opts.kind}:${normalized}`);\n return { oscillation: true, priorCount: priors.length };\n }\n return { oscillation: false, priorCount: 0 };\n}\n\n/**\n * Read-side helper: get all fix events on a target within the window.\n * Useful for the UI's self-repair panel + self-repair daemon.\n */\nexport function getRecentEventsOn(\n kind: FixTargetKind,\n target: string,\n windowMs: number = OSCILLATION_WINDOW_MS,\n): FixEvent[] {\n const normalized = normalizeTarget(kind, target);\n const cutoff = Date.now() - windowMs;\n return loadRecentEvents().filter(e =>\n e.kind === kind\n && normalizeTarget(e.kind, e.target) === normalized\n && new Date(e.at).getTime() >= cutoff,\n );\n}\n\n/**\n * All recent events, newest first. Used by the self-repair daemon to\n * spot patterns we didn't anticipate (e.g. same drive tuned 5× across\n * 5 different targets — not oscillation per-se but noteworthy).\n */\nexport function getAllRecentEvents(windowMs: number = OSCILLATION_WINDOW_MS): FixEvent[] {\n const cutoff = Date.now() - windowMs;\n return loadRecentEvents()\n .filter(e => new Date(e.at).getTime() >= cutoff)\n .sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime());\n}\n\n/** Test-only: wipe the events file. */\nexport function _resetFixEventsForTests(): void {\n try {\n if (existsSync(EVENTS_PATH)) writeFileSync(EVENTS_PATH, '', 'utf-8');\n } catch { /* ok */ }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safety/fixOscillation.ts"],"sourcesContent":["/**\n * TITAN — Fix Oscillation Detector (v4.9.0+, local hard-takeoff)\n *\n * \"The fix that made it worse\" detector.\n *\n * Every time TITAN modifies a target (file, goal, drive setpoint,\n * prompt), we record a `fixEvent`. If the SAME target gets fixed\n * TWICE within 24h, it's an oscillation — likely either:\n * - The first fix didn't actually work, TITAN is redoing it\n * - The first fix broke something adjacent, TITAN is now patching\n * the breakage\n * - The fix itself keeps oscillating because two contradictory\n * proposals disagree\n *\n * Each oscillation report goes to the kill switch, which fires when\n * ≥3 oscillations hit in a 24h window.\n *\n * This module is intentionally agent-agnostic: it records events\n * describing WHAT was changed, not WHO changed it. Target strings\n * are normalized so different call sites for the same fix collapse\n * correctly.\n *\n * Storage: <TITAN_HOME>/fix-events.jsonl (append-only, bounded).\n */\nimport { existsSync, readFileSync, appendFileSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport logger from '../utils/logger.js';\nimport { recordFixOscillation } from './killSwitch.js';\n\nconst COMPONENT = 'FixOscillation';\nconst EVENTS_PATH = join(TITAN_HOME, 'fix-events.jsonl');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type FixTargetKind =\n | 'file'\n | 'goal'\n | 'drive'\n | 'prompt'\n | 'config'\n | 'skill'\n | 'approval'\n | 'other';\n\nexport interface FixEvent {\n /** Normalized target identifier. Same target = same string. */\n target: string;\n kind: FixTargetKind;\n /** Short description of what was changed — shows up in audit. */\n detail: string;\n /** Session / agent that made the change. Empty string for system ops. */\n by: string;\n at: string;\n}\n\n// ── Storage ──────────────────────────────────────────────────────\n\nconst OSCILLATION_WINDOW_MS = 24 * 60 * 60 * 1000; // 24h\nconst EVENTS_FILE_MAX_LINES = 5000;\n\nfunction ensureDir(): void {\n try { mkdirSync(dirname(EVENTS_PATH), { recursive: true }); } catch { /* ok */ }\n}\n\n/**\n * Load recent events. We keep the file bounded — beyond MAX_LINES we\n * rewrite with a trailing tail. Cheap O(lines) per boot; acceptable\n * since bounded.\n */\nfunction loadRecentEvents(): FixEvent[] {\n if (!existsSync(EVENTS_PATH)) return [];\n try {\n const raw = readFileSync(EVENTS_PATH, 'utf-8');\n const lines = raw.split('\\n').filter(Boolean);\n const events: FixEvent[] = [];\n for (const line of lines) {\n try {\n const ev = JSON.parse(line) as FixEvent;\n if (ev.target && ev.at) events.push(ev);\n } catch { /* malformed line, skip */ }\n }\n return events;\n } catch (err) {\n logger.warn(COMPONENT, `fix-events.jsonl parse failed: ${(err as Error).message}`);\n return [];\n }\n}\n\nfunction append(event: FixEvent): void {\n ensureDir();\n appendFileSync(EVENTS_PATH, JSON.stringify(event) + '\\n', 'utf-8');\n // Bounded file: rewrite with trailing tail if exceeded.\n try {\n const lines = readFileSync(EVENTS_PATH, 'utf-8').split('\\n');\n if (lines.length > EVENTS_FILE_MAX_LINES) {\n writeFileSync(EVENTS_PATH, lines.slice(-EVENTS_FILE_MAX_LINES).join('\\n'), 'utf-8');\n }\n } catch { /* best-effort */ }\n}\n\n// ── Normalization ────────────────────────────────────────────────\n\n/**\n * Collapse variations of the same target into one identifier. E.g. a\n * file path with a trailing slash, a goal id in different case, a\n * drive with/without the \"soma:\" prefix — all normalize to one key.\n */\nexport function normalizeTarget(kind: FixTargetKind, raw: string): string {\n const t = String(raw ?? '').trim();\n switch (kind) {\n case 'file':\n // Resolve symlinks we can't from here, but collapse to absolute\n // path + strip trailing slash + lowercase on mac.\n return t.replace(/\\/+$/, '').toLowerCase();\n case 'goal':\n return t.toLowerCase();\n case 'drive':\n return t.replace(/^soma:/i, '').toLowerCase();\n case 'prompt':\n case 'config':\n case 'skill':\n return t.toLowerCase();\n case 'approval':\n return t;\n default:\n return t;\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────\n\n/**\n * Record a fix event. If this is the SECOND (or later) fix on the same\n * target within the oscillation window, the kill switch is notified via\n * `recordFixOscillation`. The kill switch itself decides when enough\n * oscillation events have accumulated to fire.\n *\n * Returns `{ oscillation: boolean, priorCount }`:\n * - oscillation=true when this event caused an oscillation to be\n * reported\n * - priorCount = number of prior fix events on the same target\n * within the window\n */\n/**\n * v5.5.6: Transient file paths where repeated writes are by design — skip\n * oscillation tracking. These are tmpfs locations and don't represent state\n * the kill-switch should care about. Examples: LLM-generated `/tmp/verdict.json`\n * artefacts that get re-emitted on every sage spawn.\n */\nconst TRANSIENT_FILE_PATTERNS: RegExp[] = [\n /^\\/tmp\\//,\n /^\\/var\\/tmp\\//,\n /^\\/private\\/tmp\\//,\n /^\\/run\\/user\\//,\n /\\.tmp(\\.|\\b)/,\n /~$/,\n];\n\nfunction isTransientPath(kind: FixTargetKind, normalized: string): boolean {\n if (kind !== 'file') return false;\n return TRANSIENT_FILE_PATTERNS.some((re) => re.test(normalized));\n}\n\nexport function recordFixEvent(opts: {\n target: string;\n kind: FixTargetKind;\n detail: string;\n by?: string;\n}): { oscillation: boolean; priorCount: number } {\n const now = new Date();\n const normalized = normalizeTarget(opts.kind, opts.target);\n\n // v5.5.6: skip transient/tmp paths — repeated writes there are by design\n if (isTransientPath(opts.kind, normalized)) {\n return { oscillation: false, priorCount: 0 };\n }\n const events = loadRecentEvents();\n const cutoff = now.getTime() - OSCILLATION_WINDOW_MS;\n const priors = events.filter(e =>\n e.kind === opts.kind &&\n normalizeTarget(e.kind, e.target) === normalized &&\n new Date(e.at).getTime() >= cutoff,\n );\n\n const event: FixEvent = {\n target: normalized,\n kind: opts.kind,\n detail: opts.detail.slice(0, 400),\n by: opts.by ?? '',\n at: now.toISOString(),\n };\n append(event);\n\n if (priors.length >= 1) {\n // Same target fixed at least once in the window — this is an\n // oscillation. Notify kill switch, which counts toward its\n // 3-in-24h threshold.\n logger.warn(COMPONENT, `Oscillation on ${opts.kind} \"${normalized.slice(0, 80)}\" (${priors.length + 1}× in 24h): ${opts.detail.slice(0, 100)}`);\n recordFixOscillation(`${opts.kind}:${normalized}`);\n return { oscillation: true, priorCount: priors.length };\n }\n return { oscillation: false, priorCount: 0 };\n}\n\n/**\n * Read-side helper: get all fix events on a target within the window.\n * Useful for the UI's self-repair panel + self-repair daemon.\n */\nexport function getRecentEventsOn(\n kind: FixTargetKind,\n target: string,\n windowMs: number = OSCILLATION_WINDOW_MS,\n): FixEvent[] {\n const normalized = normalizeTarget(kind, target);\n const cutoff = Date.now() - windowMs;\n return loadRecentEvents().filter(e =>\n e.kind === kind\n && normalizeTarget(e.kind, e.target) === normalized\n && new Date(e.at).getTime() >= cutoff,\n );\n}\n\n/**\n * All recent events, newest first. Used by the self-repair daemon to\n * spot patterns we didn't anticipate (e.g. same drive tuned 5× across\n * 5 different targets — not oscillation per-se but noteworthy).\n */\nexport function getAllRecentEvents(windowMs: number = OSCILLATION_WINDOW_MS): FixEvent[] {\n const cutoff = Date.now() - windowMs;\n return loadRecentEvents()\n .filter(e => new Date(e.at).getTime() >= cutoff)\n .sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime());\n}\n\n/** Test-only: wipe the events file. */\nexport function _resetFixEventsForTests(): void {\n try {\n if (existsSync(EVENTS_PATH)) writeFileSync(EVENTS_PATH, '', 'utf-8');\n } catch { /* ok */ }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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
|
}
|