@symerian/symi 2.6.0 → 2.6.2

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.
Files changed (188) hide show
  1. package/dist/{agents-Bi50kp6u.js → agents-qLMWAYwU.js} +4 -4
  2. package/dist/{agents.config-BcTeP94V.js → agents.config-BfjAwFCr.js} +1 -1
  3. package/dist/{agents.config-Duce7lam.js → agents.config-BmdFH4J5.js} +1 -1
  4. package/dist/{audio-preflight-DHTaS5U1.js → audio-preflight-BVaaZWkg.js} +4 -4
  5. package/dist/{audio-preflight-C40mKAp7.js → audio-preflight-CPBOQV4I.js} +4 -4
  6. package/dist/{auth-choice-Dyq-0MNq.js → auth-choice-D1u_GPfQ.js} +1 -1
  7. package/dist/{auth-choice-BFIBR4l9.js → auth-choice-zFq3WRQ0.js} +1 -1
  8. package/dist/{banner-D50f_0qf.js → banner-DpH44qlJ.js} +1 -1
  9. package/dist/build-info.json +3 -3
  10. package/dist/bundled/boot-md/handler.js +6 -6
  11. package/dist/bundled/session-memory/handler.js +6 -6
  12. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  13. package/dist/{channel-options-BFqaanEt.js → channel-options-0iOfzVR3.js} +1 -1
  14. package/dist/{channel-options-BvBcjqyk.js → channel-options-C8tKm8re.js} +1 -1
  15. package/dist/{channel-web-BQtFg4IP.js → channel-web-DYinYBz4.js} +1 -1
  16. package/dist/{channels-cli-BuTH-iVi.js → channels-cli-93tLT17t.js} +4 -4
  17. package/dist/{channels-cli-BYFQdWnL.js → channels-cli-C8HgSMTH.js} +4 -4
  18. package/dist/{chrome-DYZwl5Gv.js → chrome-D2SKJnR7.js} +5 -5
  19. package/dist/{chrome-CDJYxX5a.js → chrome-DkaXoP36.js} +5 -5
  20. package/dist/{cli-r2L-UK6y.js → cli-BYJyRW2M.js} +1 -1
  21. package/dist/{cli-eOBlVLcC.js → cli-DapZXGSB.js} +1 -1
  22. package/dist/{command-registry-D-pwcAIW.js → command-registry-9EhYc6da.js} +9 -9
  23. package/dist/{completion-cli-DIx7KyOG.js → completion-cli-BFZv3K5D.js} +2 -2
  24. package/dist/{completion-cli-DMO2OGTm.js → completion-cli-CCeQlMlC.js} +1 -1
  25. package/dist/{config-cli-BsDxqYDU.js → config-cli-DbWMIBUz.js} +1 -1
  26. package/dist/{config-cli-seaVWVru.js → config-cli-Dv2mtIeM.js} +1 -1
  27. package/dist/{configure-BmPwuHXL.js → configure-B8zV6Hxj.js} +3 -3
  28. package/dist/{configure-CqbKA0_V.js → configure-CVQf2UJE.js} +3 -3
  29. package/dist/{deliver-BH0l3UKW.js → deliver-C-37cZUe.js} +1 -1
  30. package/dist/{deliver-dODxSv3b.js → deliver-C46-vyqg.js} +1 -1
  31. package/dist/{doctor-completion-DMjs7-Qa.js → doctor-completion-BunqvoqZ.js} +1 -1
  32. package/dist/{doctor-completion-C2IV3lKi.js → doctor-completion-CLeX1kaN.js} +1 -1
  33. package/dist/entry.js +1 -1
  34. package/dist/extensionAPI.js +6 -6
  35. package/dist/{gateway-cli-BanaeKQ_.js → gateway-cli-Co-tp-WC.js} +9 -9
  36. package/dist/{gateway-cli-C-J_s559.js → gateway-cli-DMey_29a.js} +9 -9
  37. package/dist/{glass-ui-ws-DUzp9m0D.js → glass-ui-ws-C4O227UO.js} +7 -7
  38. package/dist/{glass-ui-ws-DK7x3Tz7.js → glass-ui-ws-CKb1jTZR.js} +7 -7
  39. package/dist/{health-DK6rAOhC.js → health-CDGOsNeD.js} +1 -1
  40. package/dist/{health-BpHgCv-u.js → health-CM1asUG8.js} +1 -1
  41. package/dist/{hooks-cli-D-75G_66.js → hooks-cli-DFKB3Z6X.js} +2 -2
  42. package/dist/{hooks-cli-Cin_3tFg.js → hooks-cli-Tx0TzSju.js} +2 -2
  43. package/dist/{image-CXu8W39c.js → image-CuzFLQWC.js} +1 -1
  44. package/dist/{image-CHzdaNJ4.js → image-DcpMiprB.js} +1 -1
  45. package/dist/index.js +6 -6
  46. package/dist/llm-slug-generator.js +6 -6
  47. package/dist/{models-CeKIXf5B.js → models-CiqHYwje.js} +2 -2
  48. package/dist/{models-cli-2NcPKR9A.js → models-cli-B-b-gBKG.js} +2 -2
  49. package/dist/{models-cli-DN4AVlpI.js → models-cli-B4ATSsNS.js} +3 -3
  50. package/dist/{onboard-BukRqcRH.js → onboard-B_ECX48L.js} +2 -2
  51. package/dist/{onboard-BcxDiUl_.js → onboard-DcnGZKZX.js} +2 -2
  52. package/dist/{onboard-channels-DS6s341R.js → onboard-channels-FFQPpuEN.js} +1 -1
  53. package/dist/{onboard-channels-HPxu77wp.js → onboard-channels-pfSGhg_C.js} +1 -1
  54. package/dist/{onboarding-DI-o_sax.js → onboarding-BU14cspo.js} +3 -3
  55. package/dist/{onboarding-B8uz24jt.js → onboarding-jhZxQcZ3.js} +3 -3
  56. package/dist/{onboarding.finalize-CfE_AEto.js → onboarding.finalize-B8oOMfHc.js} +6 -6
  57. package/dist/{onboarding.finalize-Bn2e61yb.js → onboarding.finalize-ByZT4dF5.js} +5 -5
  58. package/dist/{pi-embedded-B5qBa69e.js → pi-embedded-BY9AnmoP.js} +203 -27
  59. package/dist/{pi-embedded-helpers-lgx_U5KS.js → pi-embedded-helpers-B8kqLWns.js} +4 -4
  60. package/dist/{pi-embedded-helpers-pubKo8HQ.js → pi-embedded-helpers-CfqDGQ9J.js} +4 -4
  61. package/dist/{plugin-registry-NIUxULTk.js → plugin-registry-CNf1_8hj.js} +1 -1
  62. package/dist/{plugin-registry-cj99EI0k.js → plugin-registry-DXWJkJX6.js} +1 -1
  63. package/dist/plugin-sdk/{accounts-BtaOa4z_.js → accounts-BToL3HlP.js} +1 -1
  64. package/dist/plugin-sdk/{accounts-Ddm33hQm.js → accounts-D9zGZU5t.js} +3 -3
  65. package/dist/plugin-sdk/{accounts-s-AdhXVR.js → accounts-Dtszw3Zn.js} +1 -1
  66. package/dist/plugin-sdk/{active-listener-BXYeALs0.js → active-listener-bEk__wbB.js} +1 -1
  67. package/dist/plugin-sdk/{agent-scope-CYYpcO9W.js → agent-scope-C3gMMKCU.js} +2 -2
  68. package/dist/plugin-sdk/agents/model-token-filter.d.ts +10 -0
  69. package/dist/plugin-sdk/agents/pi-tools.validate-wrapper.d.ts +23 -0
  70. package/dist/plugin-sdk/agents/pi-tools.validate.d.ts +26 -0
  71. package/dist/plugin-sdk/agents/tool-loop-detection.d.ts +3 -1
  72. package/dist/plugin-sdk/{api-key-rotation-D_sMvI5W.js → api-key-rotation-CVBMpnPc.js} +1 -1
  73. package/dist/plugin-sdk/{audio-preflight-VpItkiy3.js → audio-preflight-DoQQKlxa.js} +24 -24
  74. package/dist/plugin-sdk/{bindings-C7hRtgYW.js → bindings-BbwoUGPx.js} +2 -2
  75. package/dist/plugin-sdk/{channel-activity-DoC1xtDu.js → channel-activity-Ji7f0gqq.js} +1 -1
  76. package/dist/plugin-sdk/{channel-web-CSd16cDi.js → channel-web-DZQQ0mzN.js} +22 -22
  77. package/dist/plugin-sdk/{chrome-B7RdxmJ0.js → chrome-C7c_0I5M.js} +3 -3
  78. package/dist/plugin-sdk/{chunk-Dw2XBYXv.js → chunk-jvk9axTQ.js} +1 -1
  79. package/dist/plugin-sdk/{command-format-GKSevep4.js → command-format-DSdvQ_M5.js} +1 -1
  80. package/dist/plugin-sdk/{commands-registry-COIaslGl.js → commands-registry-CQFbmUMs.js} +4 -4
  81. package/dist/plugin-sdk/config/model-profiles.d.ts +2 -0
  82. package/dist/plugin-sdk/{config-KlTNfkFF.js → config-DDkdiUOR.js} +9 -9
  83. package/dist/plugin-sdk/{deliver-BZ99UKQq.js → deliver-BZ6iNLl7.js} +10 -10
  84. package/dist/plugin-sdk/{diagnostic-05pm5Rxi.js → diagnostic-mFf4i4G9.js} +1 -1
  85. package/dist/plugin-sdk/{image-CLOPx7yW.js → image-BOYy0Ump.js} +4 -4
  86. package/dist/plugin-sdk/{image-ops-BlQR__MN.js → image-ops-Bnp6LXEx.js} +1 -1
  87. package/dist/plugin-sdk/index.js +53 -53
  88. package/dist/plugin-sdk/infra/diagnostic-events.d.ts +1 -1
  89. package/dist/plugin-sdk/{ir-BJ6BHE5b.js → ir-Fb3qpcis.js} +4 -4
  90. package/dist/plugin-sdk/{local-roots-BHLNSI8U.js → local-roots-Ckk1QfzI.js} +3 -3
  91. package/dist/plugin-sdk/logging/diagnostic-session-state.d.ts +2 -0
  92. package/dist/plugin-sdk/logging/diagnostic.d.ts +1 -1
  93. package/dist/plugin-sdk/{login-DQMXuxOk.js → login-Bh3DZPam.js} +7 -7
  94. package/dist/plugin-sdk/{login-qr-BjVZSoCi.js → login-qr-DbR7odSr.js} +9 -9
  95. package/dist/plugin-sdk/{manager-CBSBFuFz.js → manager-DckktAQ3.js} +8 -8
  96. package/dist/plugin-sdk/{manifest-registry-CPnHl_K3.js → manifest-registry-B3ugY9-f.js} +1 -1
  97. package/dist/plugin-sdk/{markdown-tables-BoYFajMu.js → markdown-tables-Dfaqilz6.js} +1 -1
  98. package/dist/plugin-sdk/{message-channel-COTAJzHd.js → message-channel-BdI5Ra9S.js} +1 -1
  99. package/dist/plugin-sdk/{model-selection-CsbEfrS0.js → model-selection-OpU8HN50.js} +4 -4
  100. package/dist/plugin-sdk/{outbound-attachment-CnslKL38.js → outbound-attachment-DnVQfTG2.js} +2 -2
  101. package/dist/plugin-sdk/{outbound-B0e8KdaR.js → outbound-rF6G8Xpr.js} +7 -7
  102. package/dist/plugin-sdk/{pi-auth-json-qWi7ZIYV.js → pi-auth-json-CJk8t14T.js} +5 -5
  103. package/dist/plugin-sdk/{pi-embedded-helpers-CW630epe.js → pi-embedded-helpers-BveUP4hk.js} +17 -17
  104. package/dist/plugin-sdk/{plugins-BNByVCIH.js → plugins-BbAvhC25.js} +4 -4
  105. package/dist/plugin-sdk/{pw-ai-CnbPIPY9.js → pw-ai-DjGUsee-.js} +8 -8
  106. package/dist/plugin-sdk/{qmd-manager-CH0XbIHf.js → qmd-manager-mjKcdwVr.js} +4 -4
  107. package/dist/plugin-sdk/{registry-D0xTnUWt.js → registry--_pGht6S.js} +2 -2
  108. package/dist/plugin-sdk/{replies-LLcQL3w6.js → replies-fI39rPGa.js} +3 -3
  109. package/dist/plugin-sdk/{reply-CkqSfQZN.js → reply-DvZeyOVA.js} +263 -87
  110. package/dist/plugin-sdk/{reply-prefix-uxfMZW4p.js → reply-prefix-BHuV5t70.js} +1 -1
  111. package/dist/plugin-sdk/{resolve-outbound-target-BiyAyTWz.js → resolve-outbound-target-BkCUbYGV.js} +2 -2
  112. package/dist/plugin-sdk/{resolve-route-B3CCBumQ.js → resolve-route-D3JH_D2N.js} +3 -3
  113. package/dist/plugin-sdk/{retry-CwQ_iIj8.js → retry-ilSJqnz9.js} +1 -1
  114. package/dist/plugin-sdk/{runner-CGBT7tgF.js → runner-BVqnEfNe.js} +9 -9
  115. package/dist/plugin-sdk/{send-C5h_YxNb.js → send-BHbXh8Ly.js} +7 -7
  116. package/dist/plugin-sdk/{send-pYqe432l.js → send-BMfJIhCk.js} +6 -6
  117. package/dist/plugin-sdk/{send-B2CEnVLL.js → send-BtANzsAo.js} +6 -6
  118. package/dist/plugin-sdk/{send-CjOBB3Vo.js → send-Bxdu6ZZy.js} +10 -10
  119. package/dist/plugin-sdk/{send-CRsR8-vO.js → send-D6LMZJ_h.js} +10 -10
  120. package/dist/plugin-sdk/{session-BsOrxiMj.js → session-kI0tzViQ.js} +4 -4
  121. package/dist/plugin-sdk/{skill-commands-ff_01_r3.js → skill-commands-DCNXVERE.js} +5 -5
  122. package/dist/plugin-sdk/{skills-_yTP47Cd.js → skills-B1GeRYlu.js} +7 -7
  123. package/dist/plugin-sdk/{sqlite-CxAR5ttJ.js → sqlite-Cq_7Cg4E.js} +1 -1
  124. package/dist/plugin-sdk/{store-BdrNabcU.js → store-Do3t33-c.js} +2 -2
  125. package/dist/plugin-sdk/{subsystem-B2uDN3TV.js → subsystem-Coz2AgU8.js} +1 -1
  126. package/dist/plugin-sdk/{tables-DNwXwNFa.js → tables-DR0NmBeH.js} +1 -1
  127. package/dist/plugin-sdk/{target-errors-Paro1BjP.js → target-errors-B7YyMnIi.js} +2 -2
  128. package/dist/plugin-sdk/{thinking-CXqf7WTe.js → thinking-DCNUIAHY.js} +5 -5
  129. package/dist/plugin-sdk/{tokens-bC3UVmVH.js → tokens-CWMflosr.js} +1 -1
  130. package/dist/plugin-sdk/{tool-images-HJ2sfZDV.js → tool-images-D7Lno-TE.js} +2 -2
  131. package/dist/plugin-sdk/{tool-loop-detection-BVA6fax-.js → tool-loop-detection-DU5sTIKg.js} +55 -5
  132. package/dist/plugin-sdk/web-BCsJFuQu.js +65 -0
  133. package/dist/plugin-sdk/{whatsapp-actions-DfseosPO.js → whatsapp-actions-CcBzDuL-.js} +21 -21
  134. package/dist/{plugins-cli-CcjxxESJ.js → plugins-cli-BiedlZMy.js} +2 -2
  135. package/dist/{plugins-cli-D8hhTHZD.js → plugins-cli-C9TYM40P.js} +2 -2
  136. package/dist/{program-D09h71pS.js → program-bnWda72r.js} +7 -7
  137. package/dist/{program-context-CLJSWBZr.js → program-context-CY1jWc5A.js} +17 -17
  138. package/dist/{prompt-select-styled-DQqZEGoo.js → prompt-select-styled-BEnZY8wI.js} +4 -4
  139. package/dist/{prompt-select-styled-zRUqu0c8.js → prompt-select-styled-B_lFTtqp.js} +4 -4
  140. package/dist/{provider-auth-helpers-LzJ2WQIc.js → provider-auth-helpers-DApJuzBd.js} +1 -1
  141. package/dist/{provider-auth-helpers-16r2WHNe.js → provider-auth-helpers-DcNKxc7D.js} +1 -1
  142. package/dist/{push-apns-DJddAK3u.js → push-apns-CKKHlj6j.js} +1 -1
  143. package/dist/{push-apns-B5xZKIxK.js → push-apns-DA7UMNQH.js} +1 -1
  144. package/dist/{pw-ai-De-KR9_s.js → pw-ai-1htA-NnS.js} +1 -1
  145. package/dist/{pw-ai-B5asscAD.js → pw-ai-m0mj2KWK.js} +1 -1
  146. package/dist/{register.agent-D7NKuUkY.js → register.agent-8lxVlmzQ.js} +5 -5
  147. package/dist/{register.agent-CP_sigRh.js → register.agent-ulu0VmOg.js} +6 -6
  148. package/dist/{register.configure-BEsGd0PR.js → register.configure-45--Sly1.js} +6 -6
  149. package/dist/{register.configure-BjRLNatb.js → register.configure-rq0h5r3X.js} +6 -6
  150. package/dist/{register.maintenance-DD6TNFtV.js → register.maintenance-CXbxRtWI.js} +8 -8
  151. package/dist/{register.maintenance-CN6KUuX7.js → register.maintenance-DyEJx7NY.js} +7 -7
  152. package/dist/{register.message-DEUcNly1.js → register.message-C7Yh1uky.js} +2 -2
  153. package/dist/{register.message-DMVC_Sqm.js → register.message-CiN_pt6K.js} +2 -2
  154. package/dist/{register.onboard-CP6RP90V.js → register.onboard-BWaRkbei.js} +4 -4
  155. package/dist/{register.onboard-J1pgV7lz.js → register.onboard-e_2hc2Rm.js} +4 -4
  156. package/dist/{register.setup-Dhc3jKpK.js → register.setup-BIIFHtF7.js} +4 -4
  157. package/dist/{register.setup-BeHpW3xI.js → register.setup-zwh90Vn-.js} +4 -4
  158. package/dist/{register.status-health-sessions-b-lWNsTM.js → register.status-health-sessions-D6t5maEr.js} +3 -3
  159. package/dist/{register.status-health-sessions-DDkC0aoW.js → register.status-health-sessions-O-sVAFHE.js} +3 -3
  160. package/dist/{register.subclis-BJqiT8Q2.js → register.subclis-DOiZocR6.js} +9 -9
  161. package/dist/{reply-D40cmAci.js → reply-05js8eGB.js} +189 -13
  162. package/dist/{run-main-BruREeZ6.js → run-main-u-Eyc8Wm.js} +14 -14
  163. package/dist/{runner-DUBExAb5.js → runner-BcQ0sF9T.js} +1 -1
  164. package/dist/{runner-WAG0M5s9.js → runner-CU9l0uJh.js} +1 -1
  165. package/dist/{server-methods-K-0MHs8x.js → server-methods-Bvl1xubo.js} +7 -7
  166. package/dist/{server-methods-Cyw_WS3A.js → server-methods-Dds-iEoY.js} +7 -7
  167. package/dist/{server-node-events-89R9Ryky.js → server-node-events-Qp-gJMET.js} +2 -2
  168. package/dist/{server-node-events-RA8RurtC.js → server-node-events-qGqpsPxm.js} +2 -2
  169. package/dist/{status-DHJLMwQN.js → status-BATOXGRi.js} +2 -2
  170. package/dist/{status-CtNKWuzg.js → status-BRSo-LY3.js} +2 -2
  171. package/dist/{status-Kv_hsY8N.js → status-BqQ9mm64.js} +1 -1
  172. package/dist/{status-BSMEjz4q.js → status-CfYa1Q9K.js} +1 -1
  173. package/dist/{subagent-registry-Cb5e_x99.js → subagent-registry-lbDgDwLy.js} +189 -13
  174. package/dist/{tool-loop-detection-BgbtzUGc.js → tool-loop-detection-C7TCF2V2.js} +53 -3
  175. package/dist/{tool-loop-detection-BU3fbtCd.js → tool-loop-detection-D7qjFnRh.js} +53 -3
  176. package/dist/{tool-loop-detection-B6j1r-Wk.js → tool-loop-detection-DPVtQOfM.js} +53 -3
  177. package/dist/{tool-loop-detection-D0kUzUGu.js → tool-loop-detection-DR_rrIA1.js} +53 -3
  178. package/dist/{unified-runner-CkJLTsTK.js → unified-runner-B4ICXRlg.js} +203 -27
  179. package/dist/{update-cli-CrRBoiVU.js → update-cli-BOaP4XyQ.js} +8 -8
  180. package/dist/{update-cli-Bl66LJZ4.js → update-cli-R8-SSkbS.js} +7 -7
  181. package/dist/{update-runner-DxpSPK-f.js → update-runner-B8UmqtLI.js} +1 -1
  182. package/dist/{update-runner-FgrqoxvV.js → update-runner-CWJ4pC1b.js} +1 -1
  183. package/dist/{web-BYRKX5Ln.js → web-BWybtnEa.js} +2 -2
  184. package/dist/{web-RePh7lRy.js → web-CQH9fSkH.js} +6 -6
  185. package/dist/{web-Czp0JS6-.js → web-CSrDbJJ7.js} +1 -1
  186. package/dist/{web-D99WHLTL.js → web-DBFW0ejP.js} +6 -6
  187. package/package.json +1 -1
  188. package/dist/plugin-sdk/web-DdTTil50.js +0 -65
@@ -7,6 +7,8 @@ const TOOL_CALL_HISTORY_SIZE = 30;
7
7
  const WARNING_THRESHOLD = 10;
8
8
  const CRITICAL_THRESHOLD = 20;
9
9
  const GLOBAL_CIRCUIT_BREAKER_THRESHOLD = 30;
10
+ const CONSECUTIVE_FAILURE_WARNING_THRESHOLD = 3;
11
+ const CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD = 5;
10
12
  const DEFAULT_LOOP_DETECTION_CONFIG = {
11
13
  enabled: false,
12
14
  historySize: TOOL_CALL_HISTORY_SIZE,
@@ -16,8 +18,11 @@ const DEFAULT_LOOP_DETECTION_CONFIG = {
16
18
  detectors: {
17
19
  genericRepeat: true,
18
20
  knownPollNoProgress: true,
19
- pingPong: true
20
- }
21
+ pingPong: true,
22
+ consecutiveFailure: true
23
+ },
24
+ consecutiveFailureWarningThreshold: CONSECUTIVE_FAILURE_WARNING_THRESHOLD,
25
+ consecutiveFailureCriticalThreshold: CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD
21
26
  };
22
27
  function asPositiveInt(value, fallback) {
23
28
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return fallback;
@@ -35,10 +40,13 @@ function resolveLoopDetectionConfig(config) {
35
40
  warningThreshold,
36
41
  criticalThreshold,
37
42
  globalCircuitBreakerThreshold,
43
+ consecutiveFailureWarningThreshold: asPositiveInt(config?.consecutiveFailureWarningThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureWarningThreshold),
44
+ consecutiveFailureCriticalThreshold: asPositiveInt(config?.consecutiveFailureCriticalThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureCriticalThreshold),
38
45
  detectors: {
39
46
  genericRepeat: config?.detectors?.genericRepeat ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.genericRepeat,
40
47
  knownPollNoProgress: config?.detectors?.knownPollNoProgress ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.knownPollNoProgress,
41
- pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong
48
+ pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong,
49
+ consecutiveFailure: (config?.detectors)?.consecutiveFailure ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.consecutiveFailure
42
50
  }
43
51
  };
44
52
  }
@@ -216,6 +224,22 @@ function canonicalPairKey(signatureA, signatureB) {
216
224
  return [signatureA, signatureB].toSorted().join("|");
217
225
  }
218
226
  /**
227
+ * Count consecutive error results for a specific tool, walking backward from
228
+ * the most recent call. A success or a call to a different tool resets the streak.
229
+ * Only counts entries that have been resolved (have a resultHash or isError flag).
230
+ */
231
+ function getConsecutiveFailureStreak(history, toolName) {
232
+ let count = 0;
233
+ for (let i = history.length - 1; i >= 0; i--) {
234
+ const entry = history[i];
235
+ if (entry.toolName !== toolName) break;
236
+ if (entry.resultHash === void 0 && entry.isError === void 0) break;
237
+ if (!entry.isError) break;
238
+ count++;
239
+ }
240
+ return count;
241
+ }
242
+ /**
219
243
  * Detect if an agent is stuck in a repetitive tool call loop.
220
244
  * Checks if the same tool+params combination has been called excessively.
221
245
  */
@@ -228,6 +252,31 @@ function detectToolCallLoop(state, toolName, params, config) {
228
252
  const noProgressStreak = noProgress.count;
229
253
  const knownPollTool = isKnownPollToolCall(toolName, params);
230
254
  const pingPong = getPingPongStreak(history, currentHash);
255
+ if (resolvedConfig.detectors.consecutiveFailure) {
256
+ const failureStreak = getConsecutiveFailureStreak(history, toolName);
257
+ if (failureStreak >= resolvedConfig.consecutiveFailureCriticalThreshold) {
258
+ log.error(`Consecutive failure circuit breaker: ${toolName} failed ${failureStreak} times in a row`);
259
+ return {
260
+ stuck: true,
261
+ level: "critical",
262
+ detector: "consecutive_failure",
263
+ count: failureStreak,
264
+ message: `CRITICAL: ${toolName} has failed ${failureStreak} consecutive times. Stop calling this tool. Explain to the user what you were trying to do and why it failed.`,
265
+ warningKey: `consecutive-failure:${toolName}`
266
+ };
267
+ }
268
+ if (failureStreak >= resolvedConfig.consecutiveFailureWarningThreshold) {
269
+ log.warn(`Consecutive failure warning: ${toolName} failed ${failureStreak} times in a row`);
270
+ return {
271
+ stuck: true,
272
+ level: "warning",
273
+ detector: "consecutive_failure",
274
+ count: failureStreak,
275
+ message: `WARNING: You have failed to call ${toolName} ${failureStreak} times consecutively. Stop retrying and explain to the user what you were trying to do. The tool arguments may contain invalid syntax from model control tokens.`,
276
+ warningKey: `consecutive-failure:${toolName}`
277
+ };
278
+ }
279
+ }
231
280
  if (noProgressStreak >= resolvedConfig.globalCircuitBreakerThreshold) {
232
281
  log.error(`Global circuit breaker triggered: ${toolName} repeated ${noProgressStreak} times with no progress`);
233
282
  return {
@@ -332,6 +381,7 @@ function recordToolCallOutcome(state, params) {
332
381
  if (call.toolName !== params.toolName || call.argsHash !== argsHash) continue;
333
382
  if (call.resultHash !== void 0) continue;
334
383
  call.resultHash = resultHash;
384
+ call.isError = params.error !== void 0;
335
385
  matched = true;
336
386
  break;
337
387
  }
@@ -10,6 +10,8 @@ const TOOL_CALL_HISTORY_SIZE = 30;
10
10
  const WARNING_THRESHOLD = 10;
11
11
  const CRITICAL_THRESHOLD = 20;
12
12
  const GLOBAL_CIRCUIT_BREAKER_THRESHOLD = 30;
13
+ const CONSECUTIVE_FAILURE_WARNING_THRESHOLD = 3;
14
+ const CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD = 5;
13
15
  const DEFAULT_LOOP_DETECTION_CONFIG = {
14
16
  enabled: false,
15
17
  historySize: TOOL_CALL_HISTORY_SIZE,
@@ -19,8 +21,11 @@ const DEFAULT_LOOP_DETECTION_CONFIG = {
19
21
  detectors: {
20
22
  genericRepeat: true,
21
23
  knownPollNoProgress: true,
22
- pingPong: true
23
- }
24
+ pingPong: true,
25
+ consecutiveFailure: true
26
+ },
27
+ consecutiveFailureWarningThreshold: CONSECUTIVE_FAILURE_WARNING_THRESHOLD,
28
+ consecutiveFailureCriticalThreshold: CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD
24
29
  };
25
30
  function asPositiveInt(value, fallback) {
26
31
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return fallback;
@@ -38,10 +43,13 @@ function resolveLoopDetectionConfig(config) {
38
43
  warningThreshold,
39
44
  criticalThreshold,
40
45
  globalCircuitBreakerThreshold,
46
+ consecutiveFailureWarningThreshold: asPositiveInt(config?.consecutiveFailureWarningThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureWarningThreshold),
47
+ consecutiveFailureCriticalThreshold: asPositiveInt(config?.consecutiveFailureCriticalThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureCriticalThreshold),
41
48
  detectors: {
42
49
  genericRepeat: config?.detectors?.genericRepeat ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.genericRepeat,
43
50
  knownPollNoProgress: config?.detectors?.knownPollNoProgress ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.knownPollNoProgress,
44
- pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong
51
+ pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong,
52
+ consecutiveFailure: (config?.detectors)?.consecutiveFailure ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.consecutiveFailure
45
53
  }
46
54
  };
47
55
  }
@@ -219,6 +227,22 @@ function canonicalPairKey(signatureA, signatureB) {
219
227
  return [signatureA, signatureB].toSorted().join("|");
220
228
  }
221
229
  /**
230
+ * Count consecutive error results for a specific tool, walking backward from
231
+ * the most recent call. A success or a call to a different tool resets the streak.
232
+ * Only counts entries that have been resolved (have a resultHash or isError flag).
233
+ */
234
+ function getConsecutiveFailureStreak(history, toolName) {
235
+ let count = 0;
236
+ for (let i = history.length - 1; i >= 0; i--) {
237
+ const entry = history[i];
238
+ if (entry.toolName !== toolName) break;
239
+ if (entry.resultHash === void 0 && entry.isError === void 0) break;
240
+ if (!entry.isError) break;
241
+ count++;
242
+ }
243
+ return count;
244
+ }
245
+ /**
222
246
  * Detect if an agent is stuck in a repetitive tool call loop.
223
247
  * Checks if the same tool+params combination has been called excessively.
224
248
  */
@@ -231,6 +255,31 @@ function detectToolCallLoop(state, toolName, params, config) {
231
255
  const noProgressStreak = noProgress.count;
232
256
  const knownPollTool = isKnownPollToolCall(toolName, params);
233
257
  const pingPong = getPingPongStreak(history, currentHash);
258
+ if (resolvedConfig.detectors.consecutiveFailure) {
259
+ const failureStreak = getConsecutiveFailureStreak(history, toolName);
260
+ if (failureStreak >= resolvedConfig.consecutiveFailureCriticalThreshold) {
261
+ log.error(`Consecutive failure circuit breaker: ${toolName} failed ${failureStreak} times in a row`);
262
+ return {
263
+ stuck: true,
264
+ level: "critical",
265
+ detector: "consecutive_failure",
266
+ count: failureStreak,
267
+ message: `CRITICAL: ${toolName} has failed ${failureStreak} consecutive times. Stop calling this tool. Explain to the user what you were trying to do and why it failed.`,
268
+ warningKey: `consecutive-failure:${toolName}`
269
+ };
270
+ }
271
+ if (failureStreak >= resolvedConfig.consecutiveFailureWarningThreshold) {
272
+ log.warn(`Consecutive failure warning: ${toolName} failed ${failureStreak} times in a row`);
273
+ return {
274
+ stuck: true,
275
+ level: "warning",
276
+ detector: "consecutive_failure",
277
+ count: failureStreak,
278
+ message: `WARNING: You have failed to call ${toolName} ${failureStreak} times consecutively. Stop retrying and explain to the user what you were trying to do. The tool arguments may contain invalid syntax from model control tokens.`,
279
+ warningKey: `consecutive-failure:${toolName}`
280
+ };
281
+ }
282
+ }
234
283
  if (noProgressStreak >= resolvedConfig.globalCircuitBreakerThreshold) {
235
284
  log.error(`Global circuit breaker triggered: ${toolName} repeated ${noProgressStreak} times with no progress`);
236
285
  return {
@@ -335,6 +384,7 @@ function recordToolCallOutcome(state, params) {
335
384
  if (call.toolName !== params.toolName || call.argsHash !== argsHash) continue;
336
385
  if (call.resultHash !== void 0) continue;
337
386
  call.resultHash = resultHash;
387
+ call.isError = params.error !== void 0;
338
388
  matched = true;
339
389
  break;
340
390
  }
@@ -9,6 +9,8 @@ const TOOL_CALL_HISTORY_SIZE = 30;
9
9
  const WARNING_THRESHOLD = 10;
10
10
  const CRITICAL_THRESHOLD = 20;
11
11
  const GLOBAL_CIRCUIT_BREAKER_THRESHOLD = 30;
12
+ const CONSECUTIVE_FAILURE_WARNING_THRESHOLD = 3;
13
+ const CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD = 5;
12
14
  const DEFAULT_LOOP_DETECTION_CONFIG = {
13
15
  enabled: false,
14
16
  historySize: TOOL_CALL_HISTORY_SIZE,
@@ -18,8 +20,11 @@ const DEFAULT_LOOP_DETECTION_CONFIG = {
18
20
  detectors: {
19
21
  genericRepeat: true,
20
22
  knownPollNoProgress: true,
21
- pingPong: true
22
- }
23
+ pingPong: true,
24
+ consecutiveFailure: true
25
+ },
26
+ consecutiveFailureWarningThreshold: CONSECUTIVE_FAILURE_WARNING_THRESHOLD,
27
+ consecutiveFailureCriticalThreshold: CONSECUTIVE_FAILURE_CRITICAL_THRESHOLD
23
28
  };
24
29
  function asPositiveInt(value, fallback) {
25
30
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return fallback;
@@ -37,10 +42,13 @@ function resolveLoopDetectionConfig(config) {
37
42
  warningThreshold,
38
43
  criticalThreshold,
39
44
  globalCircuitBreakerThreshold,
45
+ consecutiveFailureWarningThreshold: asPositiveInt(config?.consecutiveFailureWarningThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureWarningThreshold),
46
+ consecutiveFailureCriticalThreshold: asPositiveInt(config?.consecutiveFailureCriticalThreshold, DEFAULT_LOOP_DETECTION_CONFIG.consecutiveFailureCriticalThreshold),
40
47
  detectors: {
41
48
  genericRepeat: config?.detectors?.genericRepeat ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.genericRepeat,
42
49
  knownPollNoProgress: config?.detectors?.knownPollNoProgress ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.knownPollNoProgress,
43
- pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong
50
+ pingPong: config?.detectors?.pingPong ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.pingPong,
51
+ consecutiveFailure: (config?.detectors)?.consecutiveFailure ?? DEFAULT_LOOP_DETECTION_CONFIG.detectors.consecutiveFailure
44
52
  }
45
53
  };
46
54
  }
@@ -218,6 +226,22 @@ function canonicalPairKey(signatureA, signatureB) {
218
226
  return [signatureA, signatureB].toSorted().join("|");
219
227
  }
220
228
  /**
229
+ * Count consecutive error results for a specific tool, walking backward from
230
+ * the most recent call. A success or a call to a different tool resets the streak.
231
+ * Only counts entries that have been resolved (have a resultHash or isError flag).
232
+ */
233
+ function getConsecutiveFailureStreak(history, toolName) {
234
+ let count = 0;
235
+ for (let i = history.length - 1; i >= 0; i--) {
236
+ const entry = history[i];
237
+ if (entry.toolName !== toolName) break;
238
+ if (entry.resultHash === void 0 && entry.isError === void 0) break;
239
+ if (!entry.isError) break;
240
+ count++;
241
+ }
242
+ return count;
243
+ }
244
+ /**
221
245
  * Detect if an agent is stuck in a repetitive tool call loop.
222
246
  * Checks if the same tool+params combination has been called excessively.
223
247
  */
@@ -230,6 +254,31 @@ function detectToolCallLoop(state, toolName, params, config) {
230
254
  const noProgressStreak = noProgress.count;
231
255
  const knownPollTool = isKnownPollToolCall(toolName, params);
232
256
  const pingPong = getPingPongStreak(history, currentHash);
257
+ if (resolvedConfig.detectors.consecutiveFailure) {
258
+ const failureStreak = getConsecutiveFailureStreak(history, toolName);
259
+ if (failureStreak >= resolvedConfig.consecutiveFailureCriticalThreshold) {
260
+ log.error(`Consecutive failure circuit breaker: ${toolName} failed ${failureStreak} times in a row`);
261
+ return {
262
+ stuck: true,
263
+ level: "critical",
264
+ detector: "consecutive_failure",
265
+ count: failureStreak,
266
+ message: `CRITICAL: ${toolName} has failed ${failureStreak} consecutive times. Stop calling this tool. Explain to the user what you were trying to do and why it failed.`,
267
+ warningKey: `consecutive-failure:${toolName}`
268
+ };
269
+ }
270
+ if (failureStreak >= resolvedConfig.consecutiveFailureWarningThreshold) {
271
+ log.warn(`Consecutive failure warning: ${toolName} failed ${failureStreak} times in a row`);
272
+ return {
273
+ stuck: true,
274
+ level: "warning",
275
+ detector: "consecutive_failure",
276
+ count: failureStreak,
277
+ message: `WARNING: You have failed to call ${toolName} ${failureStreak} times consecutively. Stop retrying and explain to the user what you were trying to do. The tool arguments may contain invalid syntax from model control tokens.`,
278
+ warningKey: `consecutive-failure:${toolName}`
279
+ };
280
+ }
281
+ }
233
282
  if (noProgressStreak >= resolvedConfig.globalCircuitBreakerThreshold) {
234
283
  log.error(`Global circuit breaker triggered: ${toolName} repeated ${noProgressStreak} times with no progress`);
235
284
  return {
@@ -334,6 +383,7 @@ function recordToolCallOutcome(state, params) {
334
383
  if (call.toolName !== params.toolName || call.argsHash !== argsHash) continue;
335
384
  if (call.resultHash !== void 0) continue;
336
385
  call.resultHash = resultHash;
386
+ call.isError = params.error !== void 0;
337
387
  matched = true;
338
388
  break;
339
389
  }