@code-yeongyu/senpi 2026.11.2-3 → 2026.11.3-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 (307) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/bundle/chunks/anthropic-messages-LJAKRKMX.js +11 -0
  3. package/dist/bundle/chunks/{args-AOI4NEA6.js → args-GQTXDYS3.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-YLBBQXI7.js → azure-openai-responses-HDEKTPMZ.js} +1 -1
  5. package/dist/bundle/chunks/chatgpt-subscription.js +10 -2
  6. package/dist/bundle/chunks/{chunk-ED7VJGL4.js → chunk-2PGSRQBF.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-LMIETUPG.js → chunk-3LDASCJN.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-PIAY636U.js → chunk-3QPE75AR.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-FQZZGSYH.js → chunk-4ZGQUOBV.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-S2M5D5GS.js → chunk-5BMIET5O.js} +2 -2
  11. package/dist/bundle/chunks/{chunk-XLLQJALL.js → chunk-67M7HKXO.js} +1 -1
  12. package/dist/bundle/chunks/chunk-6ISOXWH2.js +2 -0
  13. package/dist/bundle/chunks/{chunk-IWM5QRAE.js → chunk-6NPK2O3C.js} +2 -2
  14. package/dist/bundle/chunks/{chunk-FO56LSUK.js → chunk-7C4W5HMU.js} +1 -1
  15. package/dist/bundle/chunks/chunk-7KIHKE2T.js +2 -0
  16. package/dist/bundle/chunks/{chunk-BW4YXS7X.js → chunk-A4KOQUHT.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-FJSJUVQQ.js → chunk-A5PZGQCP.js} +3 -3
  18. package/dist/bundle/chunks/{chunk-TBARMJ75.js → chunk-AVP6DDBF.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-KVPRQ5KN.js → chunk-B3GTID4P.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-2XBKE2BZ.js → chunk-BIWFWGUA.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-6LDUM5CL.js → chunk-DCI4QOUX.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-2YFPDNNP.js → chunk-DNZKKG4V.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-W2V7RIBF.js → chunk-E7SA75PP.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-YWD54RHU.js → chunk-EBZ6IP4V.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-N4TSAHRL.js → chunk-EVTTLPIY.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-J2OBN7TI.js → chunk-G5XUGK4B.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-IHOQR7TU.js → chunk-GKP34H3C.js} +2 -2
  28. package/dist/bundle/chunks/{chunk-PP52OWBB.js → chunk-GLTOBXZY.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-ZSVWKOAJ.js → chunk-IJGXAACA.js} +263 -237
  30. package/dist/bundle/chunks/{chunk-LGO25HPL.js → chunk-JCTODLUP.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-3ESZE3DG.js → chunk-KHBCZ6TR.js} +1 -1
  32. package/dist/bundle/chunks/chunk-KJR3XXMI.js +2 -0
  33. package/dist/bundle/chunks/{chunk-TVMW2SUP.js → chunk-KY4V3TSO.js} +2 -2
  34. package/dist/bundle/chunks/{chunk-T5ZJ2Q4O.js → chunk-MCXOJHN2.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-GIY7PGOH.js → chunk-NKI7L5UM.js} +3 -3
  36. package/dist/bundle/chunks/{chunk-L3V65XW7.js → chunk-OD7RT7G7.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-NYXXKS6D.js → chunk-OOU3BRUY.js} +3 -2
  38. package/dist/bundle/chunks/{chunk-LPHUMR6R.js → chunk-OT6ALXSJ.js} +1 -1
  39. package/dist/bundle/chunks/{chunk-XGBRBCIQ.js → chunk-Q5CKFBQT.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-F7GBSQRV.js → chunk-QPYDQ4QK.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-7FUO655J.js → chunk-QSMT5KVJ.js} +1 -1
  42. package/dist/bundle/chunks/{chunk-SAZ6E74O.js → chunk-RUOTFZQR.js} +1 -1
  43. package/dist/bundle/chunks/chunk-RWF6IJQL.js +448 -0
  44. package/dist/bundle/chunks/{chunk-P4AT7SBE.js → chunk-S5Y7NEHB.js} +1 -1
  45. package/dist/bundle/chunks/{chunk-QLTQ3UUI.js → chunk-SCBTASM4.js} +1 -1
  46. package/dist/bundle/chunks/chunk-SJWTZ3Z2.js +2 -0
  47. package/dist/bundle/chunks/{chunk-ISZ4EWIS.js → chunk-SODMQEHO.js} +1 -1
  48. package/dist/bundle/chunks/{chunk-WEGTCUO3.js → chunk-SQAQEITV.js} +1 -1
  49. package/dist/bundle/chunks/{chunk-UFVZTQKN.js → chunk-TQQG5L4D.js} +1 -1
  50. package/dist/bundle/chunks/{chunk-662SCMTH.js → chunk-U6A4X3FV.js} +1 -1
  51. package/dist/bundle/chunks/{chunk-ACIFXWJ6.js → chunk-UHUM55TR.js} +1 -1
  52. package/dist/bundle/chunks/{chunk-L5OFGHDA.js → chunk-USKWGPA6.js} +1 -1
  53. package/dist/bundle/chunks/{chunk-7ORS33CE.js → chunk-VDDJADPV.js} +1 -1
  54. package/dist/bundle/chunks/{chunk-R65P6LAT.js → chunk-VIHXG53S.js} +1 -1
  55. package/dist/bundle/chunks/{chunk-SGS76663.js → chunk-VPVD5G32.js} +1 -1
  56. package/dist/bundle/chunks/chunk-WO6I5PRF.js +3 -0
  57. package/dist/bundle/chunks/{chunk-ETCYVVVO.js → chunk-WVEIJ437.js} +1 -1
  58. package/dist/bundle/chunks/{chunk-VRWJJMHO.js → chunk-XKFG4N3Z.js} +1 -1
  59. package/dist/bundle/chunks/{chunk-57MRFI6E.js → chunk-XMWPCJU6.js} +1 -1
  60. package/dist/bundle/chunks/{chunk-GDPWQJDQ.js → chunk-XPSXUKSD.js} +1 -1
  61. package/dist/bundle/chunks/{chunk-FNBEICAA.js → chunk-YI6KRJDC.js} +1 -1
  62. package/dist/bundle/chunks/{chunk-UURGXV5B.js → chunk-YP47G4YP.js} +1 -1
  63. package/dist/bundle/chunks/{chunk-TXOR5BQL.js → chunk-YRYDCKUI.js} +1 -1
  64. package/dist/bundle/chunks/{chunk-QAM6AXA2.js → chunk-YTKZSGMQ.js} +12 -9
  65. package/dist/bundle/chunks/{chunk-V36CXIIB.js → chunk-YXL3PNTL.js} +1 -1
  66. package/dist/bundle/chunks/chunk-Z7HTHT6K.js +2 -0
  67. package/dist/bundle/chunks/{chunk-27XEDYQA.js → chunk-ZTPG27EJ.js} +1 -1
  68. package/dist/bundle/chunks/{cli-main-534A3WSC.js → cli-main-RN7YB427.js} +1 -1
  69. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-FC7QLRU2.js → cloudflare-workers-ai-system-one-N5KFNKK4.js} +1 -1
  70. package/dist/bundle/chunks/credential-accounts-BJX55MTT.js +2 -0
  71. package/dist/bundle/chunks/devin.js +10 -2
  72. package/dist/bundle/chunks/{dispatch-UY6LBJJE.js → dispatch-M7UAPGVY.js} +1 -1
  73. package/dist/bundle/chunks/{google-generative-ai-7PQ4QRHJ.js → google-generative-ai-2TZSCSIS.js} +1 -1
  74. package/dist/bundle/chunks/{google-vertex-DY7EO4EJ.js → google-vertex-7NJJOI7V.js} +1 -1
  75. package/dist/bundle/chunks/{health-write-EB4CPHXN.js → health-write-YUSPVH7U.js} +1 -1
  76. package/dist/bundle/chunks/{help-fast-path-U2PSNNWO.js → help-fast-path-DWCA77QP.js} +1 -1
  77. package/dist/bundle/chunks/host-command-KJXUTSQ7.js +2 -0
  78. package/dist/bundle/chunks/{host-lifecycle-TGG7KGQH.js → host-lifecycle-QHIJ4G2K.js} +1 -1
  79. package/dist/bundle/chunks/host-lifecycle.js +1 -1
  80. package/dist/bundle/chunks/{host-session-command-PWVPQ2OV.js → host-session-command-IPH3WXSG.js} +1 -1
  81. package/dist/bundle/chunks/{host-socket-request-F2R5P34M.js → host-socket-request-XPXJV3CS.js} +1 -1
  82. package/dist/bundle/chunks/interactive-mode-XUXMDYLA.js +2 -0
  83. package/dist/bundle/chunks/{list-tips-3D3A6GFK.js → list-tips-XJY7EL5S.js} +1 -1
  84. package/dist/bundle/chunks/{login-health-7G2DZVOV.js → login-health-226NO44U.js} +1 -1
  85. package/dist/bundle/chunks/main-P7PKMJUG.js +2 -0
  86. package/dist/bundle/chunks/{mistral-conversations-6F2RAIB2.js → mistral-conversations-QMHMHSUF.js} +1 -1
  87. package/dist/bundle/chunks/{multi-session-host-7E26R5JF.js → multi-session-host-ZQY4AN46.js} +4 -4
  88. package/dist/bundle/chunks/{native-loader-NMDTMHMA.js → native-loader-A6DNMGCK.js} +1 -1
  89. package/dist/bundle/chunks/openai-chatgpt.js +11 -3
  90. package/dist/bundle/chunks/{openai-codex-responses-VNBMMIL3.js → openai-codex-responses-GUAHKQE2.js} +1 -1
  91. package/dist/bundle/chunks/{openai-completions-JLR32APM.js → openai-completions-UPRTFM54.js} +1 -1
  92. package/dist/bundle/chunks/{openai-images-LEBNERSM.js → openai-images-IKWNS6VB.js} +1 -1
  93. package/dist/bundle/chunks/{openai-responses-JCDUNMHA.js → openai-responses-AXN5NUHH.js} +1 -1
  94. package/dist/bundle/chunks/openrouter.js +10 -2
  95. package/dist/bundle/chunks/{package-manager-cli-CBSDV36M.js → package-manager-cli-PP7L2Q5M.js} +1 -1
  96. package/dist/bundle/chunks/radius.js +10 -2
  97. package/dist/bundle/chunks/{rg-engine-JDUYBM6L.js → rg-engine-VRSXT6HG.js} +1 -1
  98. package/dist/bundle/chunks/{role-LP6LVNCY.js → role-L5J7ZPCF.js} +1 -1
  99. package/dist/bundle/chunks/rotation-stream-6INZXQFC.js +2 -0
  100. package/dist/bundle/chunks/rpc-mode-W4UATRAT.js +2 -0
  101. package/dist/bundle/chunks/{rpc-monitor-source-5TUR27NY.js → rpc-monitor-source-RMMUPBMS.js} +1 -1
  102. package/dist/bundle/chunks/{run-attached-tui-PVLUHHJX.js → run-attached-tui-RLXFGCEP.js} +1 -1
  103. package/dist/bundle/chunks/{schedule-command-W45NYTTK.js → schedule-command-ZNFEJNTT.js} +1 -1
  104. package/dist/bundle/chunks/{session-control-endpoint-LFLUNALC.js → session-control-endpoint-TWTONJWF.js} +1 -1
  105. package/dist/bundle/chunks/{session-picker-OZUINLD7.js → session-picker-KRUVCHOV.js} +1 -1
  106. package/dist/bundle/chunks/session-slot-NJOZCOHQ.js +2 -0
  107. package/dist/bundle/chunks/session-worker.js +492 -461
  108. package/dist/bundle/chunks/{state-store-SZARKD36.js → state-store-ZAFZ4JOM.js} +1 -1
  109. package/dist/bundle/chunks/{typesafe-system-one-5JYDJK6S.js → typesafe-system-one-CICIRAYN.js} +1 -1
  110. package/dist/bundle/cli.js +2 -2
  111. package/dist/bundle/host-supervisor.js +1 -1
  112. package/dist/bundle/index.js +1 -1
  113. package/dist/bundle/rpc-entry.js +1 -1
  114. package/dist/bundle/runtime-manifest.json +1 -1
  115. package/dist/core/agent-session.d.ts +8 -0
  116. package/dist/core/agent-session.js +73 -63
  117. package/dist/core/auth-storage.d.ts +19 -0
  118. package/dist/core/auth-storage.js +99 -24
  119. package/dist/core/auth-store-load-error.d.ts +29 -0
  120. package/dist/core/auth-store-load-error.js +74 -0
  121. package/dist/core/brand.d.ts +2 -0
  122. package/dist/core/brand.js +1 -0
  123. package/dist/core/credential-pool/classify.d.ts +3 -2
  124. package/dist/core/credential-pool/classify.js +10 -3
  125. package/dist/core/credential-pool/failover.d.ts +8 -0
  126. package/dist/core/credential-pool/failover.js +18 -5
  127. package/dist/core/credential-pool/rotation-stream.d.ts +1 -1
  128. package/dist/core/credential-pool/rotation-stream.js +2 -7
  129. package/dist/core/credential-pool/session-slot.d.ts +20 -0
  130. package/dist/core/credential-pool/session-slot.js +26 -0
  131. package/dist/core/credential-recovery-revision.d.ts +10 -0
  132. package/dist/core/credential-recovery-revision.js +19 -0
  133. package/dist/core/dynamic-prompt/build.d.ts +2 -1
  134. package/dist/core/dynamic-prompt/build.js +1 -2
  135. package/dist/core/dynamic-prompt/handoff.d.ts +1 -1
  136. package/dist/core/dynamic-prompt/handoff.js +9 -9
  137. package/dist/core/dynamic-prompt/identity.js +1 -1
  138. package/dist/core/dynamic-prompt/intent-gate.d.ts +1 -1
  139. package/dist/core/dynamic-prompt/intent-gate.js +8 -8
  140. package/dist/core/dynamic-prompt/policies.d.ts +1 -1
  141. package/dist/core/dynamic-prompt/policies.js +5 -7
  142. package/dist/core/dynamic-prompt/style.js +5 -5
  143. package/dist/core/dynamic-prompt/tool-section.d.ts +5 -0
  144. package/dist/core/dynamic-prompt/tool-section.js +8 -6
  145. package/dist/core/dynamic-prompt/verification.d.ts +6 -6
  146. package/dist/core/dynamic-prompt/verification.js +12 -12
  147. package/dist/core/dynamic-prompt/working-task.d.ts +1 -1
  148. package/dist/core/dynamic-prompt/working-task.js +3 -3
  149. package/dist/core/dynamic-prompt/workstation.js +1 -1
  150. package/dist/core/execution/executor-body.d.ts +22 -0
  151. package/dist/core/execution/executor-body.js +73 -0
  152. package/dist/core/execution/executor-freeze.d.ts +17 -0
  153. package/dist/core/execution/executor-freeze.js +53 -0
  154. package/dist/core/execution/executor-pipeline.d.ts +9 -0
  155. package/dist/core/execution/executor-pipeline.js +25 -0
  156. package/dist/core/execution/executor-types.d.ts +82 -0
  157. package/dist/core/execution/executor-types.js +2 -0
  158. package/dist/core/execution/executor.d.ts +8 -0
  159. package/dist/core/execution/executor.js +216 -0
  160. package/dist/core/extensions/builtin/anthropic-subscription/account-block-log.d.ts +15 -0
  161. package/dist/core/extensions/builtin/anthropic-subscription/account-block-log.js +18 -0
  162. package/dist/core/extensions/builtin/anthropic-subscription/affinity.d.ts +9 -1
  163. package/dist/core/extensions/builtin/anthropic-subscription/affinity.js +15 -4
  164. package/dist/core/extensions/builtin/anthropic-subscription/failover.d.ts +7 -1
  165. package/dist/core/extensions/builtin/anthropic-subscription/failover.js +19 -2
  166. package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +8 -2
  167. package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +25 -1
  168. package/dist/core/extensions/builtin/anthropic-subscription/provider-diagnostic.d.ts +9 -0
  169. package/dist/core/extensions/builtin/anthropic-subscription/provider-diagnostic.js +59 -0
  170. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts +13 -0
  171. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +64 -2
  172. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +7 -0
  173. package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +5 -1
  174. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.d.ts +7 -0
  175. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.js +28 -1
  176. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +2 -0
  177. package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +5 -3
  178. package/dist/core/extensions/builtin/anthropic-subscription/stream.js +4 -0
  179. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +1 -1
  180. package/dist/core/extensions/builtin/bash-timeout/index.js +3 -28
  181. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +0 -11
  182. package/dist/core/extensions/builtin/bash-timeout/timeout.js +0 -11
  183. package/dist/core/extensions/builtin/btw/index.js +3 -1
  184. package/dist/core/extensions/builtin/cache-keepalive/index.js +3 -1
  185. package/dist/core/extensions/builtin/compaction/index.js +1 -1
  186. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +3 -1
  187. package/dist/core/extensions/builtin/compaction/openai-remote.js +1 -1
  188. package/dist/core/extensions/builtin/compaction/speculative.js +3 -2
  189. package/dist/core/extensions/builtin/cursor-cli-oauth/account-command.js +3 -1
  190. package/dist/core/extensions/builtin/cursor-cli-oauth/native-bootstrap.js +3 -1
  191. package/dist/core/extensions/builtin/imagegen/index.d.ts +0 -1
  192. package/dist/core/extensions/builtin/imagegen/index.js +0 -11
  193. package/dist/core/extensions/builtin/look-at/runner.js +3 -1
  194. package/dist/core/extensions/builtin/mcp/auth/callback.js +3 -4
  195. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +2 -1
  196. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +5 -5
  197. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +5 -5
  198. package/dist/core/extensions/builtin/prompt-preset/glm-5.d.ts +1 -1
  199. package/dist/core/extensions/builtin/prompt-preset/glm-5.js +1 -1
  200. package/dist/core/extensions/builtin/terminal/extension.js +8 -1
  201. package/dist/core/extensions/builtin/terminal/notify.js +9 -3
  202. package/dist/core/extensions/builtin/terminal/prompt.d.ts +7 -1
  203. package/dist/core/extensions/builtin/terminal/prompt.js +27 -60
  204. package/dist/core/extensions/builtin/terminal/tools/bash.js +10 -2
  205. package/dist/core/extensions/builtin/todotools/normalize.d.ts +20 -3
  206. package/dist/core/extensions/builtin/todotools/normalize.js +251 -5
  207. package/dist/core/extensions/builtin/todotools/prompt.d.ts +2 -2
  208. package/dist/core/extensions/builtin/todotools/prompt.js +5 -5
  209. package/dist/core/extensions/builtin/todotools/tools/todo.js +9 -8
  210. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +0 -5
  211. package/dist/core/extensions/builtin/websearch/index.js +9 -4
  212. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts +2 -0
  213. package/dist/core/extensions/builtin/websearch/websearch/tool.js +3 -1
  214. package/dist/core/model-registry.d.ts +10 -2
  215. package/dist/core/model-registry.js +12 -4
  216. package/dist/core/model-runtime.d.ts +8 -0
  217. package/dist/core/model-runtime.js +39 -1
  218. package/dist/core/provider-failure.d.ts +6 -0
  219. package/dist/core/provider-failure.js +6 -2
  220. package/dist/core/retry-fallback/controller-types.d.ts +7 -0
  221. package/dist/core/retry-fallback/controller.d.ts +2 -0
  222. package/dist/core/retry-fallback/controller.js +13 -4
  223. package/dist/core/retry-fallback/credential-recovery.d.ts +18 -0
  224. package/dist/core/retry-fallback/credential-recovery.js +32 -0
  225. package/dist/core/retry-fallback/exhaustion.d.ts +18 -0
  226. package/dist/core/retry-fallback/exhaustion.js +20 -0
  227. package/dist/core/retry-fallback/settings.d.ts +8 -1
  228. package/dist/core/retry-fallback/settings.js +12 -6
  229. package/dist/core/session-log.js +1 -1
  230. package/dist/core/settings-manager.js +4 -1
  231. package/dist/core/system-prompt.js +1 -1
  232. package/dist/core/tools/bash.d.ts +3 -1
  233. package/dist/core/tools/bash.js +14 -4
  234. package/dist/core/tools/edit.d.ts +1 -1
  235. package/dist/core/tools/edit.js +1 -6
  236. package/dist/core/tools/grep/index.js +2 -3
  237. package/dist/core/tools/powershell.d.ts +1 -1
  238. package/dist/core/tools/powershell.js +1 -1
  239. package/dist/core/tools/read.d.ts +1 -1
  240. package/dist/core/tools/read.js +1 -1
  241. package/dist/core/tools/write.d.ts +1 -1
  242. package/dist/core/tools/write.js +1 -1
  243. package/dist/engine-trust/shell-trust.d.ts +36 -0
  244. package/dist/engine-trust/shell-trust.js +44 -0
  245. package/dist/engine-trust/transport-failure.d.ts +12 -0
  246. package/dist/engine-trust/transport-failure.js +48 -0
  247. package/dist/modes/interactive/interactive-mode.js +9 -3
  248. package/dist/modes/print-mode.js +2 -1
  249. package/dist/modes/rpc/compact-catalog-families.d.ts +5 -2
  250. package/dist/modes/rpc/compact-catalog-families.js +5 -2
  251. package/dist/modes/rpc/compact-catalog-registry.d.ts +9 -1
  252. package/dist/modes/rpc/compact-catalog-registry.js +9 -2
  253. package/dist/modes/rpc/compact-recovery-manifest-kinds.d.ts +17 -0
  254. package/dist/modes/rpc/compact-recovery-manifest-kinds.js +21 -0
  255. package/dist/modes/rpc/compact-recovery-manifest-shape.d.ts +7 -0
  256. package/dist/modes/rpc/compact-recovery-manifest.js +11 -3
  257. package/dist/modes/rpc/compact-socket-commands.js +6 -0
  258. package/dist/modes/rpc/connection-handler.js +24 -6
  259. package/dist/modes/rpc/connection-question-bridge.d.ts +3 -1
  260. package/dist/modes/rpc/connection-question-bridge.js +4 -1
  261. package/dist/modes/rpc/connection-roles.d.ts +8 -0
  262. package/dist/modes/rpc/connection-roles.js +18 -0
  263. package/dist/modes/rpc/effect-commands.d.ts +1 -0
  264. package/dist/modes/rpc/loopless-execute-grant.d.ts +20 -0
  265. package/dist/modes/rpc/loopless-execute-grant.js +26 -0
  266. package/dist/modes/rpc/loopless-runtime-settings.d.ts +30 -0
  267. package/dist/modes/rpc/loopless-runtime-settings.js +69 -0
  268. package/dist/modes/rpc/loopless-tool-call.d.ts +12 -0
  269. package/dist/modes/rpc/loopless-tool-call.js +22 -0
  270. package/dist/modes/rpc/method-classes-schema-session.d.ts +4 -0
  271. package/dist/modes/rpc/method-classes-schema-session.js +1 -0
  272. package/dist/modes/rpc/method-classes-schema.d.ts +4 -0
  273. package/dist/modes/rpc/protocol-identity.js +2 -0
  274. package/dist/modes/rpc/remote-origin.d.ts +31 -0
  275. package/dist/modes/rpc/remote-origin.js +58 -0
  276. package/dist/modes/rpc/rpc-protocol-info-types.d.ts +8 -0
  277. package/dist/modes/rpc/rpc-question-types.d.ts +7 -0
  278. package/dist/modes/rpc/rpc-question-types.js +0 -3
  279. package/dist/modes/rpc/rpc-read-response-types.d.ts +2 -1
  280. package/dist/modes/rpc/rpc-session-command-types.d.ts +3 -0
  281. package/dist/modes/rpc/rpc-session-state-types.d.ts +10 -0
  282. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  283. package/dist/modes/rpc/runtime-factory-loopless.d.ts +57 -0
  284. package/dist/modes/rpc/runtime-factory-loopless.js +131 -0
  285. package/dist/modes/rpc/session-registry.js +12 -3
  286. package/dist/modes/rpc/session-worker-client.js +2 -0
  287. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
  288. package/dist/modes/rpc/session-worker.js +2 -0
  289. package/docs/development.md +1 -1
  290. package/docs/engine-socket/fixtures/admission-routes/c-baseline.json +3 -3
  291. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  292. package/docs/engine-socket/fixtures/admission-routes/i-reset-stream.json +3 -3
  293. package/docs/engine-socket/fixtures/recovery-manifest/index.json +32 -0
  294. package/docs/engine-socket/fixtures/recovery-manifest/one-partial-family.json +118 -0
  295. package/docs/rpc.md +95 -3
  296. package/package.json +6 -6
  297. package/dist/bundle/chunks/anthropic-messages-WUPLDYD6.js +0 -10
  298. package/dist/bundle/chunks/chunk-7UC4W2T2.js +0 -2
  299. package/dist/bundle/chunks/chunk-O4GRBX4R.js +0 -3
  300. package/dist/bundle/chunks/chunk-R23WXN4L.js +0 -448
  301. package/dist/bundle/chunks/chunk-W5NLCRB3.js +0 -2
  302. package/dist/bundle/chunks/credential-accounts-OAK2E5O2.js +0 -2
  303. package/dist/bundle/chunks/host-command-WJIMRBGN.js +0 -2
  304. package/dist/bundle/chunks/interactive-mode-NPHFDPGH.js +0 -2
  305. package/dist/bundle/chunks/main-I6FJAFUP.js +0 -2
  306. package/dist/bundle/chunks/rotation-stream-ZDCNHIBE.js +0 -2
  307. package/dist/bundle/chunks/rpc-mode-GBBTN2WP.js +0 -2
@@ -0,0 +1,18 @@
1
+ import { seatDigest } from "./seat-digest.js";
2
+ import { logContinuityEvent } from "./session-observability.js";
3
+ const MAX_DETAIL_CHARS = 200;
4
+ /**
5
+ * Writes one `account_blocked` line to session.log for every block that is saved, so a pool that later
6
+ * refuses a request locally has a recorded cause (senpi#4671). The account is named by its seat digest,
7
+ * never its name (Desktop#4753); an unknown end time is left out rather than guessed.
8
+ */
9
+ export function logAccountBlocked(record) {
10
+ logContinuityEvent("account_blocked", {
11
+ seat: seatDigest(record.account),
12
+ reason: record.reason,
13
+ ...(record.until === undefined ? {} : { until: new Date(record.until).toISOString() }),
14
+ shared: record.shared,
15
+ error: record.detail.slice(0, MAX_DETAIL_CHARS),
16
+ });
17
+ }
18
+ //# sourceMappingURL=account-block-log.js.map
@@ -31,6 +31,12 @@ export type UnavailableAccount = {
31
31
  };
32
32
  /** The origin of an account pin, highest precedence first (`resolveClaudeAccountPin`). */
33
33
  export type PinSource = "cli" | "settings" | "stored";
34
+ /** One pool member's state when the pool refused a request: why it is out, and until when. */
35
+ export type BlockedAccountState = {
36
+ readonly name: string;
37
+ readonly reason: string;
38
+ readonly until: number | undefined;
39
+ };
34
40
  export declare class AllAccountsBlockedError extends Error {
35
41
  readonly soonestUnblockAt: number | undefined;
36
42
  /**
@@ -49,7 +55,9 @@ export declare class AllAccountsBlockedError extends Error {
49
55
  readonly limitedModel: string | undefined;
50
56
  /** Set only when account fallback is off, so the guidance names that account instead of the whole pool. */
51
57
  readonly unavailableAccount: UnavailableAccount | undefined;
52
- constructor(soonestUnblockAt: number | undefined, blockReason?: "auth_error", limitedModel?: string, unavailableAccount?: UnavailableAccount);
58
+ /** Every candidate's state, so the error names which account needs /login and which waits on a reset (senpi#4671). */
59
+ readonly accounts: readonly BlockedAccountState[];
60
+ constructor(soonestUnblockAt: number | undefined, blockReason?: "auth_error", limitedModel?: string, unavailableAccount?: UnavailableAccount, accounts?: readonly BlockedAccountState[]);
53
61
  }
54
62
  export declare function getAffinityKey(options: Pick<AffinityOptions, "affinityKey" | "sessionId">): string;
55
63
  /**
@@ -2,14 +2,21 @@ import { createHash } from "node:crypto";
2
2
  import { applyAccountOrder } from "@earendil-works/pi-ai/auth/pool/select";
3
3
  import { activeModelBlockUntil, pruneModelBlocks } from "../../../credential-pool/model-scope.js";
4
4
  export const DEFAULT_AFFINITY_KEY = "claude-sdk-oauth-default";
5
+ function describeAccountState(state) {
6
+ const until = state.until === undefined ? "re-login" : new Date(state.until).toISOString();
7
+ const login = state.reason === "auth_error" ? ` (run /login anthropic-subscription for ${state.name})` : "";
8
+ return `${state.name}: ${state.reason} until ${until}${login}`;
9
+ }
5
10
  export class AllAccountsBlockedError extends Error {
6
- constructor(soonestUnblockAt, blockReason, limitedModel, unavailableAccount) {
11
+ constructor(soonestUnblockAt, blockReason, limitedModel, unavailableAccount, accounts = []) {
7
12
  const until = soonestUnblockAt === undefined ? undefined : new Date(soonestUnblockAt).toISOString();
8
- super(limitedModel !== undefined && until !== undefined
13
+ const summary = limitedModel !== undefined && until !== undefined
9
14
  ? `All Anthropic Subscription accounts have hit the usage limit for model ${limitedModel} until ${until}.`
10
15
  : until === undefined
11
16
  ? "All Anthropic Subscription accounts are blocked until re-login."
12
- : `All Anthropic Subscription accounts are blocked until ${until}.`);
17
+ : `All Anthropic Subscription accounts are blocked until ${until}.`;
18
+ super(accounts.length === 0 ? summary : `${summary} Accounts: ${accounts.map(describeAccountState).join("; ")}.`);
19
+ this.accounts = accounts;
13
20
  this.limitedModel = limitedModel;
14
21
  this.name = "AllAccountsBlockedError";
15
22
  this.soonestUnblockAt = soonestUnblockAt;
@@ -119,6 +126,10 @@ export function selectAccount(accounts, options = {}) {
119
126
  return afterClear;
120
127
  throw new AllAccountsBlockedError(soonestUnblockAt(candidates, now, options.model), candidates.length > 0 && candidates.every((account) => account.blockReason === "auth_error")
121
128
  ? "auth_error"
122
- : undefined, limitedModel(candidates, now, options.model), unavailableAccount);
129
+ : undefined, limitedModel(candidates, now, options.model), unavailableAccount, candidates.map((account) => ({
130
+ name: account.name,
131
+ reason: account.blockReason ?? (isAccountBlocked(account, now) ? "blocked" : "model_limit"),
132
+ until: unblockAt(account, now, options.model),
133
+ })));
123
134
  }
124
135
  //# sourceMappingURL=affinity.js.map
@@ -1,5 +1,6 @@
1
1
  import type { CredentialStore } from "@earendil-works/pi-ai";
2
2
  import type { AccountSlot } from "./accounts.ts";
3
+ import { AllAccountsBlockedError } from "./affinity.ts";
3
4
  import type { SdkErrorClassification } from "./errors.ts";
4
5
  export declare const MAX_RATE_LIMIT_BLOCK_MS: number;
5
6
  export declare const DEFAULT_RATE_LIMIT_BLOCK_MS = 60000;
@@ -49,7 +50,12 @@ export declare class ClassifiedSdkError extends Error {
49
50
  readonly classification: SdkErrorClassification;
50
51
  readonly original: unknown;
51
52
  readonly suppressTurnRetry: boolean;
52
- constructor(classification: SdkErrorClassification, original: unknown, suppressTurnRetry: boolean);
53
+ /**
54
+ * The pool's state when this failure left no other account to try (senpi#4671). The failure stays the
55
+ * error; the pool refusal is context, so a refresh failure or a 401 is never hidden behind "all blocked".
56
+ */
57
+ readonly poolState: AllAccountsBlockedError | undefined;
58
+ constructor(classification: SdkErrorClassification, original: unknown, suppressTurnRetry: boolean, poolState?: AllAccountsBlockedError);
53
59
  }
54
60
  /**
55
61
  * Runs one attempt per account for account-level failures (a limit, billing, auth), plus at most
@@ -1,6 +1,7 @@
1
1
  import { AUTH_BLOCK_REPROBE_MS } from "../../../credential-pool/auth-reprobe.js";
2
2
  import { activeModelBlockUntil, mergeModelBlocks, modelBlockKey, pruneModelBlocks, withModelBlock, } from "../../../credential-pool/model-scope.js";
3
3
  import { usageLimitResetMs } from "../../../credential-pool/reset-time.js";
4
+ import { logAccountBlocked } from "./account-block-log.js";
4
5
  import { AllAccountsBlockedError, clearExpiredBlocks } from "./affinity.js";
5
6
  import { isConfirmedAccountUsageLimit, usageLimitBlockMs } from "./usage-limit-block.js";
6
7
  export const MAX_RATE_LIMIT_BLOCK_MS = 48 * 60 * 60 * 1_000;
@@ -91,13 +92,14 @@ export function forgetSessionBlocksFor(name) {
91
92
  return lifted;
92
93
  }
93
94
  export class ClassifiedSdkError extends Error {
94
- constructor(classification, original, suppressTurnRetry) {
95
+ constructor(classification, original, suppressTurnRetry, poolState) {
95
96
  const detail = original instanceof Error ? original.message : String(original);
96
- super(`${suppressTurnRetry ? TURN_RETRY_SUPPRESSION_PREFIX : ""}${detail}`);
97
+ super(`${suppressTurnRetry ? TURN_RETRY_SUPPRESSION_PREFIX : ""}${detail}`, poolState === undefined ? undefined : { cause: poolState });
97
98
  this.name = "ClassifiedSdkError";
98
99
  this.classification = classification;
99
100
  this.original = original;
100
101
  this.suppressTurnRetry = suppressTurnRetry;
102
+ this.poolState = poolState;
101
103
  }
102
104
  }
103
105
  function record(value) {
@@ -356,6 +358,13 @@ export async function* runFailover(options) {
356
358
  attempt--;
357
359
  continue;
358
360
  }
361
+ logAccountBlocked({
362
+ account: blocked.name,
363
+ reason: blocked.blockReason ?? "model_limit",
364
+ until: blocked.blockedUntil ?? activeModelBlockUntil(blocked.modelBlocks, options.model, now()),
365
+ shared,
366
+ detail: errorText(error),
367
+ });
359
368
  accounts = replaceAccount(accounts, blocked);
360
369
  const event = {
361
370
  account: blocked,
@@ -363,14 +372,22 @@ export async function* runFailover(options) {
363
372
  attempt: attempt + 1,
364
373
  visibleDeltaEmitted,
365
374
  };
375
+ let poolState;
366
376
  try {
367
377
  if (!visibleDeltaEmitted && attempt + 1 < attemptLimit) {
368
378
  event.nextAccount = options.selectFn(accounts);
369
379
  }
370
380
  }
381
+ catch (selectError) {
382
+ if (!(selectError instanceof AllAccountsBlockedError))
383
+ throw selectError;
384
+ poolState = selectError;
385
+ }
371
386
  finally {
372
387
  await options.onFailover?.(event);
373
388
  }
389
+ if (poolState !== undefined)
390
+ throw new ClassifiedSdkError(classification, error, visibleDeltaEmitted, poolState);
374
391
  if (visibleDeltaEmitted)
375
392
  throw classified;
376
393
  }
@@ -1,8 +1,14 @@
1
- import type { PinSource, UnavailableAccount } from "./affinity.ts";
1
+ import type { BlockedAccountState, PinSource, UnavailableAccount } from "./affinity.ts";
2
2
  import type { SdkErrorKind } from "./errors.ts";
3
3
  import type { ClaudeCodeRun } from "./executable.ts";
4
4
  export declare function noAccountGuidance(hasAnthropicCredential: boolean): string;
5
- export declare function allAccountsBlockedGuidance(soonestUnblockAt: number | undefined, blockReason?: "auth_error"): string;
5
+ /**
6
+ * The pool context of a failure that left no other account (senpi#4671): the failure stays the error, and this
7
+ * names the soonest retry (which goal mode reads) and every account's state. No refusal note: the pool did not
8
+ * refuse the request, the account failed it.
9
+ */
10
+ export declare function exhaustedPoolGuidance(soonestUnblockAt: number | undefined, accounts: readonly BlockedAccountState[]): string;
11
+ export declare function allAccountsBlockedGuidance(soonestUnblockAt: number | undefined, blockReason?: "auth_error", accounts?: readonly BlockedAccountState[]): string;
6
12
  /**
7
13
  * How to move off a pinned account. `/claude-account pin` only writes the stored pin, which a CLI or a
8
14
  * settings pin outranks (`resolveClaudeAccountPin`), so for those it would change nothing (senpi#3672).
@@ -11,7 +11,29 @@ export function noAccountGuidance(hasAnthropicCredential) {
11
11
  lines.push(" Or set CLAUDE_CODE_OAUTH_TOKEN (and _2.._N for more accounts),", " or log in with the claude CLI for ambient auth.");
12
12
  return lines.join("\n");
13
13
  }
14
- export function allAccountsBlockedGuidance(soonestUnblockAt, blockReason) {
14
+ /** One line per pool member, naming the account that needs /login (senpi#4671). */
15
+ function accountStateLines(accounts) {
16
+ return accounts.map((account) => {
17
+ const until = account.until === undefined ? "re-login" : new Date(account.until).toISOString();
18
+ const login = account.reason === "auth_error" ? ` - run /login ${PROVIDER} for this account` : "";
19
+ return ` ${account.name}: ${account.reason} until ${until}${login}`;
20
+ });
21
+ }
22
+ /**
23
+ * The pool context of a failure that left no other account (senpi#4671): the failure stays the error, and this
24
+ * names the soonest retry (which goal mode reads) and every account's state. No refusal note: the pool did not
25
+ * refuse the request, the account failed it.
26
+ */
27
+ export function exhaustedPoolGuidance(soonestUnblockAt, accounts) {
28
+ const eta = soonestUnblockAt !== undefined && Number.isFinite(soonestUnblockAt)
29
+ ? new Date(soonestUnblockAt).toISOString()
30
+ : "after re-login";
31
+ return [
32
+ ` No other Claude account is available. Soonest automatic retry: ${eta}.`,
33
+ ...accountStateLines(accounts),
34
+ ].join("\n");
35
+ }
36
+ export function allAccountsBlockedGuidance(soonestUnblockAt, blockReason, accounts = []) {
15
37
  if (blockReason === "auth_error") {
16
38
  // omo#8383: name the authentication failure; the recovery is a re-login. Only a pool whose every
17
39
  // account is auth-blocked says so (senpi#2081), and the outer credential pool reads the refusal note
@@ -22,6 +44,7 @@ export function allAccountsBlockedGuidance(soonestUnblockAt, blockReason) {
22
44
  return [
23
45
  `All Claude accounts for ${PROVIDER} are currently blocked (authentication error).`,
24
46
  ...recheck,
47
+ ...accountStateLines(accounts),
25
48
  ` /login ${PROVIDER} - re-authenticate to refresh the blocked account`,
26
49
  ` /claude-account list - inspect account states`,
27
50
  ACCOUNT_POOL_REFUSAL_NOTE,
@@ -33,6 +56,7 @@ export function allAccountsBlockedGuidance(soonestUnblockAt, blockReason) {
33
56
  return [
34
57
  `All Claude accounts for ${PROVIDER} are currently blocked (rate limit or auth errors).`,
35
58
  ` Soonest automatic retry: ${eta}.`,
59
+ ...accountStateLines(accounts),
36
60
  ` /claude-account list - inspect account states`,
37
61
  ` /login ${PROVIDER} - add another account`,
38
62
  ACCOUNT_POOL_REFUSAL_NOTE,
@@ -0,0 +1,9 @@
1
+ import { type ProviderDiagnostic } from "@earendil-works/pi-ai";
2
+ /**
3
+ * The providerDiagnostic for an Anthropic Subscription turn failure (senpi#4672), mirroring the
4
+ * chatgpt-subscription adapter (senpi#3771). A pool the extension refused locally is `auth` when every
5
+ * account waits on a new login, otherwise `rate_limit` with the soonest retry; a classified SDK failure maps
6
+ * to its provider token. The result is revalidated against the closed vocabulary, so nothing else escapes.
7
+ */
8
+ export declare function anthropicSubscriptionDiagnostic(error: unknown): ProviderDiagnostic | undefined;
9
+ //# sourceMappingURL=provider-diagnostic.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { sanitizeProviderDiagnostic, } from "@earendil-works/pi-ai";
2
+ import { isThinkingBindingMismatchText } from "@earendil-works/pi-ai/api/anthropic-thinking-binding";
3
+ import { AllAccountsBlockedError } from "./affinity.js";
4
+ import { classifySdkError } from "./errors.js";
5
+ import { ClassifiedSdkError } from "./failover.js";
6
+ /** The allowlisted provider token each classified SDK failure proves; kinds that prove nothing map to none. */
7
+ const SDK_KIND_DIAGNOSTICS = {
8
+ auth_error: { category: "auth", code: "authentication_error" },
9
+ rate_limit: { category: "rate_limit", code: "rate_limit_error" },
10
+ overloaded: { category: "provider_unavailable", code: "overloaded_error" },
11
+ billing: { category: "unknown", code: "billing_error" },
12
+ org_not_allowed: { category: "unknown", code: "permission_error" },
13
+ };
14
+ const CAUSE_DEPTH = 8;
15
+ /** The pool refusal behind `error`: the error itself or one it carries as `cause`, so wrapping never hides it. */
16
+ function poolRefusal(error) {
17
+ let current = error;
18
+ for (let depth = 0; depth < CAUSE_DEPTH && current instanceof Error; depth++) {
19
+ if (current instanceof AllAccountsBlockedError)
20
+ return current;
21
+ current = current.cause;
22
+ }
23
+ return undefined;
24
+ }
25
+ /**
26
+ * The providerDiagnostic for an Anthropic Subscription turn failure (senpi#4672), mirroring the
27
+ * chatgpt-subscription adapter (senpi#3771). A pool the extension refused locally is `auth` when every
28
+ * account waits on a new login, otherwise `rate_limit` with the soonest retry; a classified SDK failure maps
29
+ * to its provider token. The result is revalidated against the closed vocabulary, so nothing else escapes.
30
+ */
31
+ export function anthropicSubscriptionDiagnostic(error) {
32
+ const pool = poolRefusal(error);
33
+ if (pool !== undefined) {
34
+ // A pin to an account that is no longer configured is a configuration error, not a limit: no diagnostic.
35
+ if (pool.unavailableAccount?.configured === false)
36
+ return undefined;
37
+ const auth = pool.blockReason === "auth_error";
38
+ return sanitizeProviderDiagnostic({
39
+ category: auth ? "auth" : "rate_limit",
40
+ code: auth ? "account_pool_auth_blocked" : "account_pool_blocked",
41
+ evidence: "structured_code",
42
+ ...(auth || pool.soonestUnblockAt === undefined ? {} : { resetsAt: pool.soonestUnblockAt }),
43
+ });
44
+ }
45
+ const original = error instanceof ClassifiedSdkError ? error.original : error;
46
+ if (original instanceof Error && isThinkingBindingMismatchText(original.message)) {
47
+ // senpi#5587: the API refused a replayed thinking block bound to a different conversation.
48
+ return sanitizeProviderDiagnostic({
49
+ category: "invalid_request",
50
+ httpStatus: 400,
51
+ code: "invalid_request_error",
52
+ evidence: "structured_code",
53
+ });
54
+ }
55
+ const kind = error instanceof ClassifiedSdkError ? error.classification.kind : classifySdkError(error).kind;
56
+ const mapped = SDK_KIND_DIAGNOSTICS[kind];
57
+ return mapped === undefined ? undefined : sanitizeProviderDiagnostic({ ...mapped, evidence: "structured_code" });
58
+ }
59
+ //# sourceMappingURL=provider-diagnostic.js.map
@@ -5,6 +5,13 @@ export type { SessionTurnResult } from "./session-turn-types.ts";
5
5
  export declare const DEFAULT_PRE_REPLAY_MAX_MESSAGES = 64;
6
6
  export declare const DEFAULT_PRE_REPLAY_MAX_BYTES: number;
7
7
  export declare const SESSION_TURN_ABORT_GRACE_MS = 1000;
8
+ /**
9
+ * How long a turn submitted while the previous turn of the same session is still active waits for that turn to
10
+ * settle before the submission is refused. The common case settles within the abort grace above: the agent loop
11
+ * starts the next turn (a steer, a follow-up, a monitor notification) right after it interrupted or finished
12
+ * consuming the previous one, while the pump still waits for the SDK's terminal result of that turn.
13
+ */
14
+ export declare const SESSION_TURN_ADMISSION_WAIT_MS = 10000;
8
15
  export interface SessionTurnRequest {
9
16
  message: SDKUserMessage["message"];
10
17
  signal?: AbortSignal;
@@ -16,5 +23,11 @@ export declare class ConcurrentSessionTurnAdmissionError extends Error {
16
23
  readonly code = "claude_sdk_oauth_concurrent_turn_admission";
17
24
  constructor(sessionId: string);
18
25
  }
26
+ /**
27
+ * Waits for `settled`, bounded by SESSION_TURN_ADMISSION_WAIT_MS: "admitted" once it settles, "aborted" when
28
+ * `signal` aborts first, and a ConcurrentSessionTurnAdmissionError when the bound passes, so a stuck pump cannot
29
+ * hold the session's callers forever.
30
+ */
31
+ export declare function waitForTurnAdmission(sessionId: string, settled: Promise<unknown>, signal: AbortSignal | undefined, schedule?: (callback: () => void, delayMs: number) => () => void): Promise<"admitted" | "aborted">;
19
32
  export declare function submitSessionTurn(registry: AnthropicSubscriptionSessionRegistry, entry: AnthropicSubscriptionSessionEntry, request: SessionTurnRequest, limits?: PreReplayBufferLimits): Promise<SessionTurnResult>;
20
33
  //# sourceMappingURL=session-registry-pump.d.ts.map
@@ -8,6 +8,13 @@ import { SessionTurnAttributionError } from "./session-turn-types.js";
8
8
  export const DEFAULT_PRE_REPLAY_MAX_MESSAGES = 64;
9
9
  export const DEFAULT_PRE_REPLAY_MAX_BYTES = 256 * 1024;
10
10
  export const SESSION_TURN_ABORT_GRACE_MS = 1_000;
11
+ /**
12
+ * How long a turn submitted while the previous turn of the same session is still active waits for that turn to
13
+ * settle before the submission is refused. The common case settles within the abort grace above: the agent loop
14
+ * starts the next turn (a steer, a follow-up, a monitor notification) right after it interrupted or finished
15
+ * consuming the previous one, while the pump still waits for the SDK's terminal result of that turn.
16
+ */
17
+ export const SESSION_TURN_ADMISSION_WAIT_MS = 10_000;
11
18
  export class ConcurrentSessionTurnAdmissionError extends Error {
12
19
  constructor(sessionId) {
13
20
  super(`Concurrent Anthropic Subscription turn admission for session ${sessionId}`);
@@ -154,12 +161,66 @@ async function runPump(registry, entry) {
154
161
  failTurn(registry, entry, error instanceof Error ? error : new Error(String(error)));
155
162
  }
156
163
  }
164
+ /**
165
+ * Waits for `settled`, bounded by SESSION_TURN_ADMISSION_WAIT_MS: "admitted" once it settles, "aborted" when
166
+ * `signal` aborts first, and a ConcurrentSessionTurnAdmissionError when the bound passes, so a stuck pump cannot
167
+ * hold the session's callers forever.
168
+ */
169
+ export function waitForTurnAdmission(sessionId, settled, signal, schedule = scheduleAbort) {
170
+ return new Promise((resolve, reject) => {
171
+ let done = false;
172
+ let cancelTimer;
173
+ const finish = (run) => {
174
+ if (done)
175
+ return;
176
+ done = true;
177
+ cancelTimer?.();
178
+ signal?.removeEventListener("abort", onAbort);
179
+ run();
180
+ };
181
+ const onAbort = () => finish(() => resolve("aborted"));
182
+ if (signal?.aborted) {
183
+ onAbort();
184
+ return;
185
+ }
186
+ // The scheduler may fire synchronously (tests inject one); `done` then stops every later step.
187
+ cancelTimer = schedule(() => finish(() => reject(new ConcurrentSessionTurnAdmissionError(sessionId))), SESSION_TURN_ADMISSION_WAIT_MS);
188
+ if (done)
189
+ return;
190
+ signal?.addEventListener("abort", onAbort, { once: true });
191
+ const admitted = () => finish(() => resolve("admitted"));
192
+ void settled.then(admitted, admitted);
193
+ });
194
+ }
195
+ /**
196
+ * A turn submitted while the session's previous turn is still active waits for that turn to settle, bounded by
197
+ * SESSION_TURN_ADMISSION_WAIT_MS, then submits. One SDK session runs one turn at a time, so the second request is
198
+ * logically after the first; refusing it (the behavior before this guard) surfaced as a provider failure and moved
199
+ * the session to a fallback model for no reason of the provider's. An abort of the waiting request resolves it as
200
+ * an aborted turn; a previous turn that does not settle in time keeps the refusal.
201
+ */
202
+ async function admitAfter(registry, entry, active, settled, request, limits) {
203
+ const generation = entry.generation;
204
+ const outcome = await waitForTurnAdmission(entry.senpiSessionId, settled, request.signal, request.scheduleAbort);
205
+ if (outcome === "aborted")
206
+ return { uuid: createSessionUuid(), messages: [], aborted: true };
207
+ if (!registry.isCurrentGeneration(entry.senpiSessionId, generation)) {
208
+ throw new Error("Anthropic Subscription session closed while a turn waited for admission");
209
+ }
210
+ if (currentTurn(entry) === active)
211
+ entry.activeTurn = null;
212
+ return submitSessionTurn(registry, entry, request, limits);
213
+ }
157
214
  export function submitSessionTurn(registry, entry, request, limits = {
158
215
  maxMessages: DEFAULT_PRE_REPLAY_MAX_MESSAGES,
159
216
  maxBytes: DEFAULT_PRE_REPLAY_MAX_BYTES,
160
217
  }) {
161
- if (currentTurn(entry))
162
- throw new ConcurrentSessionTurnAdmissionError(entry.senpiSessionId);
218
+ const active = currentTurn(entry);
219
+ if (active) {
220
+ if (!active.settled)
221
+ throw new ConcurrentSessionTurnAdmissionError(entry.senpiSessionId);
222
+ return admitAfter(registry, entry, active, active.settled, request, limits);
223
+ }
163
224
  if (entry.state === "STARTING")
164
225
  transitionToIdleSynced(entry);
165
226
  transitionToTurnWaiting(entry);
@@ -199,6 +260,7 @@ export function submitSessionTurn(registry, entry, request, limits = {
199
260
  limits,
200
261
  };
201
262
  });
263
+ turn.settled = promise.then(() => undefined, () => undefined);
202
264
  entry.activeTurn = turn;
203
265
  if (!entry.pumpTask)
204
266
  entry.pumpTask = runPump(registry, entry);
@@ -178,6 +178,13 @@ export function registerSessionRegistry(pi) {
178
178
  await writeStoredBinding(sessionFile, stored);
179
179
  });
180
180
  pi.on("session_shutdown", (event, ctx) => {
181
+ // A config hot-reload keeps the session and this module's registry (session_start skips
182
+ // "reload" above), and the resident query captures no per-load state: its hooks and tool
183
+ // handlers are module constants. The next turn's fingerprint check restarts the process
184
+ // only when the reload changed the prompt, tools or options. Closing here re-spawned Claude
185
+ // Code on every reload, and on a real account re-attached the claude.ai connectors.
186
+ if (event.reason === "reload")
187
+ return;
181
188
  closeSession(ctx.sessionManager.getSessionId(), event.reason);
182
189
  });
183
190
  pi.on("session_extensions_removed", async (_event, ctx) => {
@@ -12,7 +12,7 @@ import { bindingFromEntry, bindingInvalidationReason, getBinding } from "./sessi
12
12
  import { closeSession, getOrCreateSession, getSession, isIdleExpired, } from "./session-registry.js";
13
13
  import { admitRestoredBinding } from "./session-restored-admission.js";
14
14
  import { buildDeltaPromptBlocks, configFingerprint, sentHashesForEntry, sentMessageHashes, sentMessages, } from "./session-sync.js";
15
- import { createSessionTurnAttempt } from "./session-turn-attempt.js";
15
+ import { createSessionTurnAttempt, waitForActiveTurn } from "./session-turn-attempt.js";
16
16
  function userMessage(content) {
17
17
  return { role: "user", content };
18
18
  }
@@ -46,6 +46,10 @@ async function createResidentAttempt(input, auth) {
46
46
  const sessionId = input.streamOptions.sessionId;
47
47
  const messages = sentMessages(input.context);
48
48
  const hashes = sentMessageHashes(messages);
49
+ // Continuity and the delta are read from the settled previous turn, never from one still in flight.
50
+ const pending = getSession(sessionId);
51
+ if (pending)
52
+ await waitForActiveTurn(pending, input.streamOptions.signal);
49
53
  const existing = getSession(sessionId);
50
54
  const fingerprint = configFingerprint(auth.options, input.context, auth.authLane, auth.accountName);
51
55
  const residentHashes = existing ? (sentHashesForEntry(existing) ?? hashes) : hashes;
@@ -4,6 +4,13 @@ import { type AnthropicSubscriptionSessionEntry } from "./session-registry.ts";
4
4
  type StagedContinuityDecision = {
5
5
  emit(): void;
6
6
  };
7
+ /**
8
+ * Holds a new attempt until the attempt running the entry's previous turn finished and recorded the sent stream
9
+ * (`recordSyncedStream`), bounded and abortable like the pump's own admission wait. The caller decides
10
+ * continuity and the delta only after this, so the delta starts after the earlier turn's message instead of
11
+ * sending it again (senpi#5015 review).
12
+ */
13
+ export declare function waitForActiveTurn(entry: AnthropicSubscriptionSessionEntry, signal: AbortSignal | undefined): Promise<void>;
7
14
  /** Claude Code's wording for a fork point absent from its transcript; the captured id is the dead one. */
8
15
  export declare const RESUME_MESSAGE_MISSING: RegExp;
9
16
  export declare function createSessionTurnAttempt(entry: AnthropicSubscriptionSessionEntry, message: SDKUserMessage["message"], hashes: readonly string[], signal: AbortSignal | undefined, staged: StagedContinuityDecision,
@@ -2,8 +2,24 @@ import { BoundedAsyncQueue, SESSION_STREAM_QUEUE_CAPACITY } from "./bounded-queu
2
2
  import { sdkResultFailure } from "./errors.js";
3
3
  import { bindingFromEntry, forgetBinding, rememberBinding } from "./session-reattach.js";
4
4
  import { closeSession, isCurrentGeneration, sessionRegistry, } from "./session-registry.js";
5
- import { submitSessionTurn } from "./session-registry-pump.js";
5
+ import { submitSessionTurn, waitForTurnAdmission } from "./session-registry-pump.js";
6
6
  import { recordSyncedStream, sentHashPrefixDigest } from "./session-sync.js";
7
+ /** Pending while the attempt that submitted the entry's latest turn has not yet recorded what that turn sent. */
8
+ const attemptBookkeeping = new WeakMap();
9
+ /**
10
+ * Holds a new attempt until the attempt running the entry's previous turn finished and recorded the sent stream
11
+ * (`recordSyncedStream`), bounded and abortable like the pump's own admission wait. The caller decides
12
+ * continuity and the delta only after this, so the delta starts after the earlier turn's message instead of
13
+ * sending it again (senpi#5015 review).
14
+ */
15
+ export async function waitForActiveTurn(entry, signal) {
16
+ const pending = attemptBookkeeping.get(entry);
17
+ if (!pending)
18
+ return;
19
+ if ((await waitForTurnAdmission(entry.senpiSessionId, pending, signal)) === "aborted") {
20
+ throw new Error("Anthropic Subscription turn aborted while waiting for the previous turn");
21
+ }
22
+ }
7
23
  function successfulTurn(messages) {
8
24
  return messages.some((message) => message.type === "result" && message.subtype === "success" && sdkResultFailure(message) === undefined);
9
25
  }
@@ -91,6 +107,16 @@ servedLineage) {
91
107
  return {
92
108
  messages: (async function* () {
93
109
  const queue = new BoundedAsyncQueue(SESSION_STREAM_QUEUE_CAPACITY);
110
+ let markBookkept;
111
+ const bookkeeping = new Promise((resolve) => {
112
+ markBookkept = resolve;
113
+ });
114
+ attemptBookkeeping.set(entry, bookkeeping);
115
+ const bookkept = () => {
116
+ if (attemptBookkeeping.get(entry) === bookkeeping)
117
+ attemptBookkeeping.delete(entry);
118
+ markBookkept();
119
+ };
94
120
  const completion = submitSessionTurn(sessionRegistry, entry, {
95
121
  message,
96
122
  signal,
@@ -147,6 +173,7 @@ servedLineage) {
147
173
  finally {
148
174
  // A consumer that stops reading early must not leave the pump waiting for room.
149
175
  queue.close();
176
+ bookkept();
150
177
  }
151
178
  })(),
152
179
  discard: () => {
@@ -23,6 +23,8 @@ export interface ActiveTurn {
23
23
  resolve: (result: SessionTurnResult) => void;
24
24
  reject: (error: Error) => void;
25
25
  limits: PreReplayBufferLimits;
26
+ /** Settles (never rejects) once the turn resolved or rejected; a turn submitted behind it waits here. */
27
+ settled?: Promise<void>;
26
28
  }
27
29
  export interface PreReplayBufferLimits {
28
30
  maxMessages: number;
@@ -2,7 +2,7 @@ import { AllAccountsBlockedError } from "./affinity.js";
2
2
  import { errorAccountContext } from "./account-context.js";
3
3
  import { classifySdkError } from "./errors.js";
4
4
  import { ClassifiedSdkError } from "./failover.js";
5
- import { allAccountsBlockedGuidance, claudeCodeVersionFloorGuidance, sdkErrorGuidance, singleAccountUnavailableGuidance, switchAccountLine, } from "./guidance.js";
5
+ import { allAccountsBlockedGuidance, claudeCodeVersionFloorGuidance, exhaustedPoolGuidance, sdkErrorGuidance, singleAccountUnavailableGuidance, switchAccountLine, } from "./guidance.js";
6
6
  import { ClaudeSignInRequiredError } from "./sign-in-again.js";
7
7
  export function withAuthGuidance(error, message, ran, now = Date.now()) {
8
8
  // The sign-in-again prompt is the remedy; the generic auth_error hint ("token was rejected... refresh") contradicts it.
@@ -10,7 +10,7 @@ export function withAuthGuidance(error, message, ran, now = Date.now()) {
10
10
  return message;
11
11
  if (error instanceof AllAccountsBlockedError) {
12
12
  const guidance = error.unavailableAccount === undefined
13
- ? allAccountsBlockedGuidance(error.soonestUnblockAt, error.blockReason)
13
+ ? allAccountsBlockedGuidance(error.soonestUnblockAt, error.blockReason, error.accounts)
14
14
  : singleAccountUnavailableGuidance(error.unavailableAccount, error.soonestUnblockAt, error.blockReason);
15
15
  // The model-limit sentence is how the fallback chain learns the limit binds this model only (senpi#5007).
16
16
  return error.limitedModel === undefined
@@ -28,7 +28,9 @@ export function withAuthGuidance(error, message, ran, now = Date.now()) {
28
28
  guidance = `${selected} ${action}`;
29
29
  }
30
30
  const versionGuidance = claudeCodeVersionFloorGuidance(message, ran);
31
- const hints = [guidance, versionGuidance].filter((hint) => hint !== undefined);
31
+ const pool = error instanceof ClassifiedSdkError ? error.poolState : undefined;
32
+ const poolGuidance = pool && exhaustedPoolGuidance(pool.soonestUnblockAt, pool.accounts);
33
+ const hints = [guidance, versionGuidance, poolGuidance].filter((hint) => hint !== undefined);
32
34
  return hints.length > 0 ? `${message}\n${hints.join("\n")}` : message;
33
35
  }
34
36
  /**
@@ -13,6 +13,7 @@ import { refusalError } from "./refusal.js";
13
13
  import { getSdkBoundary, loadClaudeAgentSdk } from "./sdk-boundary.js";
14
14
  import { emitContinuityObservation } from "./session-observability.js";
15
15
  import { forgetBinding } from "./session-reattach.js";
16
+ import { anthropicSubscriptionDiagnostic } from "./provider-diagnostic.js";
16
17
  import { requireAnthropicSubscriptionCwd } from "./session-cwd.js";
17
18
  import { residentSessionMessages } from "./session-stream.js";
18
19
  import { loadAnthropicSubscriptionProviderSettingsFromDisk, resolveCompactionOwner, resumeModeSource, } from "./settings.js";
@@ -268,6 +269,9 @@ export function streamAnthropicSubscription(model, context, options) {
268
269
  if (billed)
269
270
  updateUsage(model, output, billed, "query");
270
271
  output.errorMessage = withAuthGuidance(error, errorMessage(error), claudeCodeRun);
272
+ const diagnostic = output.stopReason === "error" ? anthropicSubscriptionDiagnostic(error) : undefined;
273
+ if (diagnostic !== undefined)
274
+ output.providerDiagnostic = diagnostic;
271
275
  markColdSeedOverflow(output, model, coldSeedAttempt, coldSeedEstimate, options?.sessionId);
272
276
  stream.push({ type: "error", reason: output.stopReason, error: output });
273
277
  }
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "../../types.ts";
2
2
  export type { BashTimeoutDefaults, BashToolInputLike } from "./timeout.ts";
3
- export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.ts";
3
+ export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, resolveBashTimeoutDefaults } from "./timeout.ts";
4
4
  export default function bashTimeoutExtension(pi: ExtensionAPI): void;
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,5 @@
1
- import { isAnthropicBashEnabled } from "../anthropic-bash/index.js";
2
- import { isEvalOnlyRouting } from "../eval-only-routing.js";
3
- import { resolveForegroundWindowSeconds } from "../terminal/tools/foreground-window.js";
4
- import { applyBashTimeout, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.js";
5
- export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.js";
1
+ import { applyBashTimeout, resolveBashTimeoutDefaults } from "./timeout.js";
2
+ export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, resolveBashTimeoutDefaults } from "./timeout.js";
6
3
  export default function bashTimeoutExtension(pi) {
7
4
  const env = typeof process !== "undefined" ? process.env : {};
8
5
  const defaults = resolveBashTimeoutDefaults(env);
@@ -17,28 +14,6 @@ export default function bashTimeoutExtension(pi) {
17
14
  input.timeout = timeout;
18
15
  }
19
16
  });
20
- /**
21
- * Native Anthropic bash replaces the PTY `bash` tool and the terminal
22
- * extension steps aside with it, so nothing implements the auto-detach the
23
- * policy would otherwise advertise.
24
- */
25
- const resolveWindow = (ctx) => {
26
- if (isAnthropicBashEnabled() && ctx?.model?.api === "anthropic-messages")
27
- return undefined;
28
- return resolveForegroundWindowSeconds(env);
29
- };
30
- pi.on("before_agent_start", async (event, ctx) => {
31
- // A session with no `bash` tool has nothing this policy can describe. A host that exposes
32
- // only the handler surface (no getAllTools) keeps the policy, as the terminal section does.
33
- if (typeof pi.getAllTools === "function" && !pi.getAllTools().some((tool) => tool.name === "bash")) {
34
- return undefined;
35
- }
36
- const foregroundWindowSeconds = resolveWindow(ctx);
37
- const prompt = buildBashTimeoutPrompt(defaults, {
38
- ...(foregroundWindowSeconds === undefined ? {} : { foregroundWindowSeconds }),
39
- evalOnly: isEvalOnlyRouting(pi),
40
- });
41
- return { systemPrompt: `${event.systemPrompt}${prompt}` };
42
- }, { previewSafe: true });
17
+ // The timeout facts reach the model through the terminal section (senpi#5335); this extension adds no prompt text.
43
18
  }
44
19
  //# sourceMappingURL=index.js.map