@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
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_INIT_PHASE } from "./todo-types.js";
1
2
  const CONFLICTING_SHAPES = 'conflicting shapes. Use {"op":"init","list":[...]} to replace the list or {"op":"append","phase":"<active phase>","items":[...]} to add tasks.';
2
3
  function isRecord(value) {
3
4
  return typeof value === "object" && value !== null;
@@ -66,12 +67,257 @@ function unwrapTodoList(value) {
66
67
  }
67
68
  return Array.isArray(current) && current.length > 0 ? current : value;
68
69
  }
69
- /** The todo tool's `prepareArguments`: returns the arguments with `list` unwrapped to its canonical shape. */
70
- export function prepareTodoArguments(args) {
71
- if (!isRecord(args) || Array.isArray(args) || !("list" in args))
70
+ /**
71
+ * Reserved string key under which `prepareTodoArguments` carries its correction notices to
72
+ * `execute`. `validateToolArguments` clones prepared arguments with `structuredClone`, which
73
+ * drops symbol keys, so the channel must be a string key; `execute` strips it before
74
+ * `normalizeTodoParams` and prepends the notices. The key is never persisted or rendered,
75
+ * but prepared arguments flow to the agent loop's `beforeToolCall`/`afterToolCall` hooks, so
76
+ * extension `tool_call`/`tool_result` hooks may observe it on the arguments they receive.
77
+ */
78
+ const TODO_PREPARED_CORRECTIONS_KEY = "__todoPreparedCorrections";
79
+ function isPhaseRecord(value) {
80
+ return isRecord(value) && typeof value.phase === "string" && Array.isArray(value.items);
81
+ }
82
+ function isContentRecord(value) {
83
+ return isRecord(value) && typeof value.content === "string";
84
+ }
85
+ function quoted(values) {
86
+ return values.map((value) => JSON.stringify(value)).join(", ");
87
+ }
88
+ /**
89
+ * Rewrites near-canonical `list` entries (senpi#5332) to the canonical shape: an entry whose
90
+ * `items` are all nested `{phase, items}` records is replaced by them, `items` entries that
91
+ * are records carrying a string `content` become that string, entries with missing or empty
92
+ * `items` and stray string entries are dropped, and entries with tasks but no usable `phase`
93
+ * name get sequential default names. An `items` array that mixes strings with phase records
94
+ * is ambiguous and returns `null`, as does nothing else here: the caller then leaves the
95
+ * whole call untouched so validation rejects it as before. The input array and its entries
96
+ * are never mutated; when nothing changes, the same array reference is returned.
97
+ */
98
+ function normalizeTodoListEntries(input, notices) {
99
+ let list = [...input];
100
+ let changed = false;
101
+ const promotedOuter = [];
102
+ for (let depth = 0; depth < 5; depth++) {
103
+ const next = [];
104
+ let promotedThisPass = false;
105
+ for (const entry of list) {
106
+ if (isPhaseRecord(entry) && entry.items.length > 0 && entry.items.every(isPhaseRecord)) {
107
+ next.push(...entry.items);
108
+ promotedOuter.push(entry.phase);
109
+ promotedThisPass = true;
110
+ }
111
+ else {
112
+ next.push(entry);
113
+ }
114
+ }
115
+ if (!promotedThisPass)
116
+ break;
117
+ list = next;
118
+ }
119
+ if (promotedOuter.length > 0) {
120
+ changed = true;
121
+ notices.push(`[auto-corrected] "list" ${promotedOuter.length === 1 ? "entry" : "entries"} ${quoted(promotedOuter)} contained only nested phases; promoted the nested phases to top level. Each "list" entry is exactly one phase: {"phase":"<name>","items":["<task>", ...]}`);
122
+ }
123
+ let unwrappedContent = 0;
124
+ for (let index = 0; index < list.length; index++) {
125
+ const entry = list[index];
126
+ if (!isPhaseRecord(entry))
127
+ continue;
128
+ const mapped = entry.items.map((item) => {
129
+ if (isContentRecord(item) && !isPhaseRecord(item)) {
130
+ unwrappedContent += 1;
131
+ return item.content;
132
+ }
133
+ return item;
134
+ });
135
+ // Strings and nested phase records in one items array cannot be told apart, so no
136
+ // correction may guess: leave the whole call to validation.
137
+ if (mapped.some((item) => typeof item === "string") && mapped.some(isPhaseRecord))
138
+ return null;
139
+ if (!mapped.every((item) => typeof item === "string"))
140
+ continue;
141
+ if (mapped.some((item, position) => item !== entry.items[position])) {
142
+ list[index] = { ...entry, items: mapped };
143
+ changed = true;
144
+ }
145
+ }
146
+ if (unwrappedContent > 0) {
147
+ notices.push(`[auto-corrected] unwrapped ${unwrappedContent} "items" ${unwrappedContent === 1 ? "entry" : "entries"} carrying a "content" field into their task strings. "items" holds only task strings.`);
148
+ }
149
+ const withoutEmptyPhases = list.filter((entry) => {
150
+ if (!isRecord(entry))
151
+ return true;
152
+ if (typeof entry.phase !== "string" && !("items" in entry))
153
+ return true;
154
+ return entry.items !== undefined && (!Array.isArray(entry.items) || entry.items.length > 0);
155
+ });
156
+ if (withoutEmptyPhases.length !== list.length) {
157
+ changed = true;
158
+ const dropped = list.length - withoutEmptyPhases.length;
159
+ notices.push(`[auto-corrected] dropped ${dropped} "list" ${dropped === 1 ? "entry" : "entries"} with no tasks. Every phase needs at least one task in "items".`);
160
+ list = withoutEmptyPhases;
161
+ }
162
+ const hasObjectEntries = list.some((entry) => isRecord(entry));
163
+ const strayStrings = list.filter((entry) => typeof entry === "string");
164
+ if (strayStrings.length > 0 && hasObjectEntries) {
165
+ changed = true;
166
+ notices.push(`[auto-corrected] dropped ${strayStrings.length} stray ${strayStrings.length === 1 ? "entry" : "entries"} from "list". "list" holds only phase objects: {"phase":"<name>","items":["<task>", ...]}`);
167
+ list = list.filter((entry) => typeof entry !== "string");
168
+ }
169
+ const usedNames = new Set();
170
+ for (const entry of list) {
171
+ if (isRecord(entry) && typeof entry.phase === "string" && entry.phase.trim() !== "")
172
+ usedNames.add(entry.phase);
173
+ }
174
+ const generatedNames = [];
175
+ for (let index = 0; index < list.length; index++) {
176
+ const entry = list[index];
177
+ if (!isRecord(entry) ||
178
+ !Array.isArray(entry.items) ||
179
+ entry.items.length === 0 ||
180
+ !entry.items.every((item) => typeof item === "string") ||
181
+ (typeof entry.phase === "string" && entry.phase.trim() !== "")) {
182
+ continue;
183
+ }
184
+ let counter = 1;
185
+ let name = DEFAULT_INIT_PHASE;
186
+ while (usedNames.has(name)) {
187
+ counter += 1;
188
+ name = `${DEFAULT_INIT_PHASE} ${counter}`;
189
+ }
190
+ usedNames.add(name);
191
+ generatedNames.push(name);
192
+ const { phase: _replaced, ...rest } = entry;
193
+ list[index] = { phase: name, ...rest };
194
+ changed = true;
195
+ }
196
+ if (generatedNames.length > 0) {
197
+ notices.push(`[auto-corrected] named ${generatedNames.length} "list" ${generatedNames.length === 1 ? "entry" : "entries"} without a phase name: ${quoted(generatedNames)}. Pass "phase" explicitly: {"phase":"<name>","items":["<task>", ...]}`);
198
+ }
199
+ return changed ? list : input;
200
+ }
201
+ /**
202
+ * The todo tool's `prepareArguments`: rewrites near-canonical shapes (senpi#5148 wrappers,
203
+ * senpi#5332 list/op corrections) to the canonical one before schema validation. Any call a
204
+ * rule does not correct is returned as the SAME reference, and a correction that would leave
205
+ * `list` empty is abandoned entirely so the malformed call still fails validation instead of
206
+ * clearing the todo list. A caller-sent `TODO_PREPARED_CORRECTIONS_KEY` is never trusted
207
+ * (senpi#5332): it is dropped on a copy before any rule runs, and when that removal is the
208
+ * only change the stripped copy is returned instead of the same reference.
209
+ */
210
+ export function prepareTodoArguments(input) {
211
+ if (!isRecord(input) || Array.isArray(input))
212
+ return input;
213
+ const { [TODO_PREPARED_CORRECTIONS_KEY]: _callerSent, ...stripped } = input;
214
+ const args = TODO_PREPARED_CORRECTIONS_KEY in input ? stripped : input;
215
+ const notices = [];
216
+ let draft;
217
+ const revise = (mutate) => {
218
+ draft ??= { ...args };
219
+ mutate(draft);
220
+ };
221
+ const rawOp = args.op;
222
+ let op = rawOp;
223
+ if (typeof rawOp === "string" && !isOperation(rawOp)) {
224
+ // Only stray leading/trailing characters that are neither letters nor digits are
225
+ // stripped ("view}", " done."); digits survive, so "rm 2" and " 2rm" stay invalid
226
+ // ops, and the middle must already be exactly a known operation ("re-move").
227
+ const stripped = rawOp.replace(/^[^a-z0-9]+|[^a-z0-9]+$/gi, "");
228
+ // A corrected rm/done/drop/start without a non-blank "task"/"phase" would act on every
229
+ // task (senpi#5332 review): leave such a call for validation to reject unchanged.
230
+ const carriesTarget = nonBlank(args.task) !== undefined || nonBlank(args.phase) !== undefined;
231
+ const needsTarget = stripped === "rm" || stripped === "done" || stripped === "drop" || stripped === "start";
232
+ if (isOperation(stripped) && (!needsTarget || carriesTarget)) {
233
+ op = stripped;
234
+ revise((draftArgs) => {
235
+ draftArgs.op = stripped;
236
+ });
237
+ notices.push(`[auto-corrected] "op" ${JSON.stringify(rawOp)} is not a known operation; used "${stripped}" after stripping stray leading or trailing non-alphanumeric characters. Pass "op" exactly: init, start, done, rm, drop, append, view.`);
238
+ }
239
+ }
240
+ // A `list` always means init, so its rules also run when op is missing. The top-level
241
+ // `items` rules do not: an op-less `items` call is only inferred as init against an empty
242
+ // list and as append with a phase, so promoting or unwrapping it here would bypass those
243
+ // guards (senpi#5332 review). Only an explicit "init" — after the op correction above —
244
+ // may adopt the `items` rules.
245
+ if (op === undefined || op === "init") {
246
+ if ("list" in args) {
247
+ let listValue = args.list;
248
+ if (typeof listValue === "string") {
249
+ try {
250
+ const parsed = JSON.parse(listValue);
251
+ if (Array.isArray(parsed) || isRecord(parsed)) {
252
+ listValue = parsed;
253
+ notices.push('[auto-corrected] "list" was sent as a JSON string; parsed it. Pass "list" as an array of phases.');
254
+ }
255
+ }
256
+ catch {
257
+ // Unparseable: leave the string for validation to reject.
258
+ }
259
+ }
260
+ let candidate = unwrapTodoList(listValue);
261
+ if (Array.isArray(candidate)) {
262
+ const normalized = normalizeTodoListEntries(candidate, notices);
263
+ if (normalized === null)
264
+ return args;
265
+ candidate = normalized;
266
+ }
267
+ // A correction must never widen a malformed call into a list clear.
268
+ if (Array.isArray(candidate) && candidate.length === 0)
269
+ return args;
270
+ if (candidate !== args.list) {
271
+ const adopted = candidate;
272
+ revise((draftArgs) => {
273
+ draftArgs.list = adopted;
274
+ });
275
+ }
276
+ }
277
+ }
278
+ if (op === "init" && args.list === undefined && Array.isArray(args.items) && args.items.length > 0) {
279
+ const items = args.items;
280
+ if (items.every(isPhaseRecord)) {
281
+ const normalized = normalizeTodoListEntries([...items], notices);
282
+ if (normalized === null || normalized.length === 0)
283
+ return args;
284
+ const adopted = normalized;
285
+ revise((draftArgs) => {
286
+ draftArgs.list = adopted;
287
+ delete draftArgs.items;
288
+ });
289
+ notices.push('[auto-corrected] "items" held phase records; moved them to "list". Pass phases under "list": {"op":"init","list":[{"phase":"<name>","items":["<task>", ...]}]}');
290
+ }
291
+ else {
292
+ const mapped = items.map((item) => (isContentRecord(item) && !isPhaseRecord(item) ? item.content : item));
293
+ if (mapped.some((item) => typeof item === "string") && mapped.some(isPhaseRecord))
294
+ return args;
295
+ const unwrapped = items.filter((item) => isContentRecord(item) && !isPhaseRecord(item)).length;
296
+ if (unwrapped > 0 && mapped.every((item) => typeof item === "string")) {
297
+ revise((draftArgs) => {
298
+ draftArgs.items = mapped;
299
+ });
300
+ notices.push(`[auto-corrected] unwrapped ${unwrapped} top-level "items" ${unwrapped === 1 ? "entry" : "entries"} carrying a "content" field into their task strings. "items" holds only task strings.`);
301
+ }
302
+ }
303
+ }
304
+ if (!draft)
72
305
  return args;
73
- const unwrapped = unwrapTodoList(args.list);
74
- return unwrapped === args.list ? args : { ...args, list: unwrapped };
306
+ return notices.length > 0 ? { ...draft, [TODO_PREPARED_CORRECTIONS_KEY]: [...notices] } : draft;
307
+ }
308
+ /**
309
+ * Splits the prepare-time correction notices off prepared arguments (see
310
+ * `TODO_PREPARED_CORRECTIONS_KEY`): the arguments without the reserved key, plus the notices
311
+ * to prepend to `normalizeTodoParams`'s own corrections.
312
+ */
313
+ export function splitPreparedCorrections(args) {
314
+ const { [TODO_PREPARED_CORRECTIONS_KEY]: notices, ...rest } = args;
315
+ return {
316
+ args: rest,
317
+ corrections: Array.isArray(notices)
318
+ ? notices.filter((notice) => typeof notice === "string")
319
+ : [],
320
+ };
75
321
  }
76
322
  function nonBlank(value) {
77
323
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
@@ -1,3 +1,3 @@
1
- export declare const TODO_TOOL_DESCRIPTION = "**Tasks referenced by verbatim content string, NEVER an auto-generated ID: no \"task-1\"/\"task-N\" exists. Pass the content text in the task field.**\n\nOn each completion the earliest still-open task (in phase order) auto-promotes to in_progress.\nCompleting tasks out of phase order can move this pointer **back** to an earlier phase. This is expected; completed tasks are never reverted.\n\n## Operations\n\n| op | Required fields | Effect |\n|---|---|---|\n| init | list: [{phase, items: string[]}] | Initialize full list (replaces existing) |\n| init | items: string[] | Flattened single-phase init |\n| start | task | Mark in progress |\n| done | task or phase | Mark completed |\n| drop | task or phase | Mark abandoned |\n| rm | task or phase (optional) | Remove task or phase; omit both to clear |\n| append | phase?, items: string[] | Append tasks to phase; lazily creates phase |\n| view | - | Read-only: echo list |\n\nExample: {\"op\":\"init\",\"list\":[{\"phase\":\"Setup\",\"items\":[\"Survey code\",\"Write tests\"]}]}\n\n## Anatomy\n- **Task content**: 5\u201310 words; what, not how. Unique identifier.\n- **Phase name**: short noun phrase (e.g. Foundation, Auth, Verification). Unique identifier. NEVER prefix 1., A), Phase 1:.\n\n## Rules\n- Mark tasks done immediately after finishing, then re-check the list against the newest user message. Complete phases in order.\n- NEVER end a turn with a todo call as its only tool call - batch it with the real work: init with the first reads/edits, each done/start with the next action.\n- Blocked? append a task to the active phase, or drop.\n- Keep task/phase strings stable once introduced. done/start/drop take the task's EXACT text: copy it verbatim from the latest todo result; when it is lost, view echoes the list.\n\n## When to create a list\n- User explicitly requests one\n- User provides a set of tasks\n- New instructions arrive mid-task (reconcile before acting): keep what they don't conflict with, amend what they contradict, append what they add; replace only on explicit redirect\n\n<critical>\nUser hands you a multi-step plan (phased todo, numbered/bulleted checklist, or \"N bugs/items/tasks\"):\n- You MUST init the list with EVERY item as its own task before working.\n- Enumerate all; NEVER summarize into fewer tasks, sample \"the important ones\", drop items, or track the rest from memory.\n</critical>";
2
- export declare const TASK_MANAGEMENT_SECTION = "\n<Task_Management>\n## Todo Management\n\nA request with three or more distinct steps gets a phased todo before the first edit - investigation, implementation, verification, final report - in the todo tool; its description carries the operations, anatomy, and rules. Mark each item done the moment it finishes and reconcile the list against the newest user message before ending a turn.\n\n## Evidence\n- File edit: inspect the changed files and diagnostics.\n- Build command: require exit code 0.\n- Test run: require passing output, or state the pre-existing failure.\n</Task_Management>\n";
1
+ export declare const TODO_TOOL_DESCRIPTION = "**Tasks referenced by verbatim content string, NEVER an auto-generated ID: no \"task-1\"/\"task-N\" exists. Pass the content text in the task field.**\n\nOn each completion the earliest still-open task (in phase order) auto-promotes to in_progress.\nCompleting tasks out of phase order can move this pointer **back** to an earlier phase. This is expected; completed tasks are never reverted.\n\n## Operations\n\n| op | Required fields | Effect |\n|---|---|---|\n| init | list: [{phase, items: string[]}] | Initialize full list (replaces existing). Each list entry is exactly one phase; items holds only task strings |\n| init | items: string[] | Flattened single-phase init |\n| start | task | Mark in progress |\n| done | task or phase | Mark completed |\n| drop | task or phase | Mark abandoned |\n| rm | task or phase (optional) | Remove task or phase; omit both to clear |\n| append | phase?, items: string[] | Append tasks to phase; lazily creates phase |\n| view | - | Read-only: echo list |\n\nExample: {\"op\":\"init\",\"list\":[{\"phase\":\"Setup\",\"items\":[\"Survey code\",\"Write tests\"]}]}\n\n## Anatomy\n- **Task content**: 5\u201310 words; what, not how. Unique identifier.\n- **Phase name**: short noun phrase (e.g. Foundation, Auth, Verification). Unique identifier. NEVER prefix 1., A), Phase 1:.\n\n## Rules\n- Mark tasks done immediately after finishing, then re-check the list against the newest user message. Complete phases in order.\n- NEVER end a turn with a todo call as its only tool call - batch it with the real work: init with the first reads/edits, each done/start with the next action.\n- Blocked? append a task to the active phase, or drop.\n- Keep task/phase strings stable once introduced. done/start/drop take the task's EXACT text: copy it verbatim from the latest todo result; when it is lost, view echoes the list.\n\n## When to create a list\n- User explicitly requests one\n- User provides a set of tasks\n- New instructions arrive mid-task (reconcile before acting): keep what they don't conflict with, amend what they contradict, append what they add; replace only on explicit redirect\n\n<critical>\nUser hands you a multi-step plan (phased todo, numbered/bulleted checklist, or \"N bugs/items/tasks\"):\n- You MUST init the list with EVERY item as its own task before working.\n- Enumerate all; NEVER summarize into fewer tasks, sample \"the important ones\", drop items, or track the rest from memory.\n</critical>";
2
+ export declare const TASK_MANAGEMENT_SECTION = "\n<Task_Management>\n## Todo Management\n\nRequest with 3+ distinct steps -> phased todo in the todo tool before first edit: investigation, implementation, verification, final report.\n\n## Evidence\n- File edit: inspect changed files + diagnostics.\n- Build: exit code 0.\n- Test run: passing output, or state the pre-existing failure.\n</Task_Management>\n";
3
3
  //# sourceMappingURL=prompt.d.ts.map
@@ -11,7 +11,7 @@ Completing tasks out of phase order can move this pointer **back** to an earlier
11
11
 
12
12
  | op | Required fields | Effect |
13
13
  |---|---|---|
14
- | init | list: [{phase, items: string[]}] | Initialize full list (replaces existing) |
14
+ | init | list: [{phase, items: string[]}] | Initialize full list (replaces existing). Each list entry is exactly one phase; items holds only task strings |
15
15
  | init | items: string[] | Flattened single-phase init |
16
16
  | start | task | Mark in progress |
17
17
  | done | task or phase | Mark completed |
@@ -46,12 +46,12 @@ export const TASK_MANAGEMENT_SECTION = `
46
46
  <Task_Management>
47
47
  ## Todo Management
48
48
 
49
- A request with three or more distinct steps gets a phased todo before the first edit - investigation, implementation, verification, final report - in the todo tool; its description carries the operations, anatomy, and rules. Mark each item done the moment it finishes and reconcile the list against the newest user message before ending a turn.
49
+ Request with 3+ distinct steps -> phased todo in the todo tool before first edit: investigation, implementation, verification, final report.
50
50
 
51
51
  ## Evidence
52
- - File edit: inspect the changed files and diagnostics.
53
- - Build command: require exit code 0.
54
- - Test run: require passing output, or state the pre-existing failure.
52
+ - File edit: inspect changed files + diagnostics.
53
+ - Build: exit code 0.
54
+ - Test run: passing output, or state the pre-existing failure.
55
55
  </Task_Management>
56
56
  `;
57
57
  //# sourceMappingURL=prompt.js.map
@@ -6,7 +6,7 @@ import { Text } from "@earendil-works/pi-tui";
6
6
  import { Type } from "typebox";
7
7
  import { partialStrikethrough, strikeRevealCount } from "../../../../../modes/interactive/components/todo-strike.js";
8
8
  import { resolvePromptSurface } from "../../../../dynamic-prompt/build.js";
9
- import { normalizeTodoParams, prepareTodoArguments } from "../normalize.js";
9
+ import { normalizeTodoParams, prepareTodoArguments, splitPreparedCorrections } from "../normalize.js";
10
10
  import { TODO_TOOL_DESCRIPTION } from "../prompt.js";
11
11
  import { applyParams, captureListAsk, clonePhases, findTaskByContent, formatHandoffCue, formatSummary, getCompletionTransitions, getTodoMarker, handoffMomentOf, nextActionableTask, sanitizeTodoText, TODO_STATE_ENTRY_TYPE, } from "../state.js";
12
12
  const TodoOperationSchema = Type.Union([
@@ -212,24 +212,25 @@ export function registerTodoTool(pi, accessors) {
212
212
  label: "Todo",
213
213
  description: TODO_TOOL_DESCRIPTION,
214
214
  promptSnippet: "Track phased tasks with one op-based todo tool; reference tasks by their exact content.",
215
- promptGuidelines: [
216
- "Use one todo operation at a time, batched with the real work; never end a turn with a todo call as its only tool call.",
217
- "Reference tasks and phases by their exact content/name; use view when the text is uncertain.",
218
- "Mark work done immediately and use drop for tasks that are no longer needed.",
219
- ],
220
215
  parameters: TODO_PARAMS_SCHEMA,
221
216
  prepareArguments: (args) => prepareTodoArguments(args),
222
217
  executionMode: "sequential",
223
218
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
224
219
  const previousPhases = clonePhases(accessors.getCurrentPhases());
225
220
  const previousAsk = accessors.getCurrentAsk();
226
- const normalized = normalizeTodoParams(params, previousPhases);
221
+ // prepareTodoArguments carries its notices on the prepared args under a reserved key
222
+ // (#5332). The key is stripped here before normalizing and is never persisted or
223
+ // rendered, but the prepared arguments — key included — flow to the agent loop's
224
+ // beforeToolCall/afterToolCall hooks, so extension tool_call/tool_result hooks may
225
+ // observe it on the arguments they receive.
226
+ const prepared = splitPreparedCorrections(params);
227
+ const normalized = normalizeTodoParams(prepared.args, previousPhases);
227
228
  if (normalized.error || !normalized.entry) {
228
229
  const error = normalized.error ?? 'Missing "op". Example: {"op":"init","list":[{"phase":"Setup","items":["..."]}]}';
229
230
  throw new Error(`${error}\n\n${formatSummary(previousPhases, [], true, previousAsk)}`);
230
231
  }
231
232
  const entry = normalized.entry;
232
- const corrections = normalized.corrections;
233
+ const corrections = [...prepared.corrections, ...normalized.corrections];
233
234
  const readOnly = entry.op === "view";
234
235
  const errors = [];
235
236
  const applied = readOnly
@@ -19,11 +19,6 @@ export const webfetch = defineTool({
19
19
  description: "Fetches content from a URL and returns it as markdown, plain text, or HTML. " +
20
20
  "Network use is bounded by timeout and response size limits.",
21
21
  promptSnippet: "webfetch: retrieve URL content as markdown, text, or html",
22
- promptGuidelines: [
23
- "Use webfetch when a specific URL must be retrieved.",
24
- "Prefer markdown format unless raw HTML or plain text is explicitly needed.",
25
- "The tool is read-only and does not modify files.",
26
- ],
27
22
  parameters: Params,
28
23
  async execute(_toolCallId, params, signal, onUpdate, _ctx) {
29
24
  const format = parseWebfetchFormat(params.format);
@@ -8,7 +8,7 @@ import { loadWebsearchConfig } from "./websearch/config.js";
8
8
  import { attemptRouteLabel } from "./websearch/route-attempts.js";
9
9
  import { providerEntryLabel } from "./websearch/search.js";
10
10
  import { resolveNativeSearchModel } from "./websearch/search-model.js";
11
- import { createWebSearchTool } from "./websearch/tool.js";
11
+ import { createWebSearchTool, WEB_SEARCH_CITE_GUIDELINE } from "./websearch/tool.js";
12
12
  const STATUS_KEY = "pi-websearch";
13
13
  const WIDGET_KEY = "pi-websearch";
14
14
  const NATIVE_BYPASS_MESSAGE = "Native provider web search is handled by the built-in provider extension.";
@@ -127,13 +127,18 @@ export default function (pi) {
127
127
  }
128
128
  if (!hasServerSearch(ctx.model))
129
129
  return undefined;
130
- const systemPrompt = event.systemPrompt
130
+ const failed = hasServerSearchFailed(ctx.sessionManager, ctx.model);
131
+ const withoutSections = event.systemPrompt
131
132
  .replace(ANTHROPIC_WEB_SEARCH_SECTION, "")
132
133
  .replace(OPENAI_WEB_SEARCH_SECTION, "");
134
+ // Server search: the section below carries the cite rule, so the tool's guideline bullet goes.
135
+ const systemPrompt = failed
136
+ ? withoutSections
137
+ : withoutSections.replace(`- ${WEB_SEARCH_CITE_GUIDELINE}\n`, "");
133
138
  return {
134
- systemPrompt: `${systemPrompt}\n## Web Search\n${hasServerSearchFailed(ctx.sessionManager, ctx.model)
139
+ systemPrompt: `${systemPrompt}\n## Web Search\n${failed
135
140
  ? "Use the standalone web_search tool when current information is needed. Native search failed for this session model."
136
- : "Use the provider's in-turn server web search when current information is needed."}`,
141
+ : "web_search = provider server-side search. Use when current info needed; cite returned URLs in final answer."}`,
137
142
  };
138
143
  }, { previewSafe: true });
139
144
  pi.on("before_provider_request", (event, ctx) => {
@@ -12,6 +12,8 @@ type WebSearchTool = ReturnType<typeof defineTool<typeof Params, SearchRenderDet
12
12
  export interface WebSearchToolOptions {
13
13
  onSearchComplete?: (details: SearchDetails) => void;
14
14
  }
15
+ /** The one Tool Guidelines bullet; the server-search `## Web Search` section states it instead (senpi#5335). */
16
+ export declare const WEB_SEARCH_CITE_GUIDELINE = "web_search used -> cite returned URLs in final answer.";
15
17
  export declare function createWebSearchTool(getConfig: ConfigProvider, options?: WebSearchToolOptions): WebSearchTool;
16
18
  export declare const web_search: import("../../../types.ts").ToolDefinition<Type.TObject<{
17
19
  query: Type.TString;
@@ -40,6 +40,8 @@ function formatSearchProgressText(details) {
40
40
  function searchErrorDetails(query, error, reason) {
41
41
  return { phase: "error", query, error, ...(reason ? { reason } : {}) };
42
42
  }
43
+ /** The one Tool Guidelines bullet; the server-search `## Web Search` section states it instead (senpi#5335). */
44
+ export const WEB_SEARCH_CITE_GUIDELINE = "web_search used -> cite returned URLs in final answer.";
43
45
  export function createWebSearchTool(getConfig, options = {}) {
44
46
  let routingState;
45
47
  let routingKey = "";
@@ -49,7 +51,7 @@ export function createWebSearchTool(getConfig, options = {}) {
49
51
  label: "Web Search",
50
52
  description: "Search the web for current information and return source URLs for citation.",
51
53
  promptSnippet: "Search the web for current information, documentation, news, or external facts.",
52
- promptGuidelines: ["After using web_search, cite relevant returned URLs in the final answer."],
54
+ promptGuidelines: [WEB_SEARCH_CITE_GUIDELINE],
53
55
  parameters: Params,
54
56
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
55
57
  if (params.allowed_domains?.length && params.blocked_domains?.length) {
@@ -43,12 +43,20 @@ export declare class ModelRegistry {
43
43
  /**
44
44
  * The live auth check reads the in-memory credentials. When the last read found the store
45
45
  * locked they were never loaded, so re-read now (bounded by the sync lock budget) instead
46
- * of answering from the empty fallback for the rest of the process.
46
+ * of answering from the empty fallback for the rest of the process. A store that could not be
47
+ * loaded is re-read the same way, so a repaired auth.json is seen without a restart (#4413).
47
48
  */
48
49
  private retryBusyCredentialRead;
49
50
  getUpstreamModelId(model: Model<Api>): string | undefined;
50
51
  getServiceTier(model: Model<Api>): "auto" | "flex" | "priority" | "ultrafast" | undefined;
51
- getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth>;
52
+ /**
53
+ * Request auth for a direct provider call. A caller acting for a session passes its id so the
54
+ * key belongs to the account that session's turns use; without it the stored credential is
55
+ * resolved as for session-less callers.
56
+ */
57
+ getApiKeyAndHeaders(model: Model<Api>, options?: {
58
+ sessionId?: string;
59
+ }): Promise<ResolvedRequestAuth>;
52
60
  getProviderAuthStatus(provider: string): AuthStatus;
53
61
  /** `getProviderAuthStatus` plus whether it is a non-stored sign-in, from one resolution (#3537). */
54
62
  resolveProviderAuth(provider: string): {
@@ -59,10 +59,11 @@ export class ModelRegistry {
59
59
  /**
60
60
  * The live auth check reads the in-memory credentials. When the last read found the store
61
61
  * locked they were never loaded, so re-read now (bounded by the sync lock budget) instead
62
- * of answering from the empty fallback for the rest of the process.
62
+ * of answering from the empty fallback for the rest of the process. A store that could not be
63
+ * loaded is re-read the same way, so a repaired auth.json is seen without a restart (#4413).
63
64
  */
64
65
  retryBusyCredentialRead() {
65
- if (this.authStorage.isCredentialStoreBusy())
66
+ if (this.authStorage.isCredentialStoreBusy() || this.authStorage.getLoadError())
66
67
  this.authStorage.reload();
67
68
  }
68
69
  getUpstreamModelId(model) {
@@ -71,9 +72,16 @@ export class ModelRegistry {
71
72
  getServiceTier(model) {
72
73
  return this.runtime.getCompatibilityRequestConfig(model).serviceTier;
73
74
  }
74
- async getApiKeyAndHeaders(model) {
75
+ /**
76
+ * Request auth for a direct provider call. A caller acting for a session passes its id so the
77
+ * key belongs to the account that session's turns use; without it the stored credential is
78
+ * resolved as for session-less callers.
79
+ */
80
+ async getApiKeyAndHeaders(model, options = {}) {
75
81
  try {
76
- const resolution = await this.runtime.getAuth(model);
82
+ const resolution = options.sessionId === undefined
83
+ ? await this.runtime.getAuth(model)
84
+ : await this.runtime.getSessionAuth(model, options.sessionId);
77
85
  const compatibility = this.runtime.getCompatibilityRequestConfig(model);
78
86
  if (!resolution) {
79
87
  if (compatibility.authHeader) {
@@ -168,6 +168,14 @@ export declare class ModelRuntime implements Models {
168
168
  isFallbackEligible(providerId: string): boolean;
169
169
  getAuth(providerId: string, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;
170
170
  getAuth(model: AnyModel, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;
171
+ /**
172
+ * Auth for a request a session sends outside the rotation stream (compaction, /btw, look-at,
173
+ * cache keepalive). The key must belong to the account the session's own turns use, so a pooled
174
+ * provider resolves the slot the stream would pick for this session; without a pool, or when
175
+ * the stream would not rotate either, it is `getAuth(model)`.
176
+ */
177
+ getSessionAuth(model: Model<Api>, sessionId: string): Promise<AuthResult | undefined>;
178
+ private sessionSlotAuth;
171
179
  private enqueueCredentialOperation;
172
180
  private synchronizeCredentialState;
173
181
  setRuntimeApiKey(providerId: string, apiKey: string, options?: AuthOperationOptions): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { dirname, join } from "node:path";
2
2
  import { clampThinkingLevel, createModels, getCurrentTools, isModelType, lazyStream, ModelsError, normalizeContext, normalizeProviderId, providerNotConfiguredMessage, setWireIdentity, wrapStreamWithModelRecovery, } from "@earendil-works/pi-ai";
3
3
  import * as builtinProviderCatalog from "@earendil-works/pi-ai/providers/all";
4
+ import { setOAuthPageBrand } from "@earendil-works/pi-ai/utils/oauth-page";
4
5
  import { assertChatModel, assertClassifierModel, assertImageModel, classifierErrorResult, imageErrorResult, } from "@earendil-works/pi-ai/utils/model-operations";
5
6
  import { APP_NAME, BRAND, getAgentDir } from "../config.js";
6
7
  import { operationSignal, raceWithAbortSignal } from "../utils/abort.js";
@@ -23,6 +24,7 @@ import { createVirtualModel, findLatestResponse, isVirtualModel, withVirtualMode
23
24
  // The product's identity must ride outgoing requests. This lives here because the AI package
24
25
  // is already part of this module's graph; the CLI bootstrap deliberately does not import it.
25
26
  setWireIdentity(BRAND?.userAgent ?? APP_NAME);
27
+ setOAuthPageBrand({ name: APP_NAME, ...(BRAND?.logo ? { logo: BRAND.logo } : {}) });
26
28
  /** Availability re-reads after a busy credential store: 250, 500, 1000, 2000, 4000 ms. */
27
29
  const BUSY_AVAILABILITY_RETRY_LIMIT = 5;
28
30
  const BUSY_AVAILABILITY_RETRY_BASE_MS = 250;
@@ -550,6 +552,42 @@ export class ModelRuntime {
550
552
  },
551
553
  };
552
554
  }
555
+ /**
556
+ * Auth for a request a session sends outside the rotation stream (compaction, /btw, look-at,
557
+ * cache keepalive). The key must belong to the account the session's own turns use, so a pooled
558
+ * provider resolves the slot the stream would pick for this session; without a pool, or when
559
+ * the stream would not rotate either, it is `getAuth(model)`.
560
+ */
561
+ async getSessionAuth(model, sessionId) {
562
+ return this.getAuth(model, (await this.sessionSlotAuth(model, sessionId)) ?? {});
563
+ }
564
+ async sessionSlotAuth(model, sessionId) {
565
+ // The stream's own synchronous admission check, so a provider the stream would not rotate costs
566
+ // nothing beyond getAuth. Remote compaction's before_provider_request hook calls this on every
567
+ // request to such a model; there a pooled provider pays one extra credential read and two slot
568
+ // listings (each reads the pool's health state) before getAuth.
569
+ if (!this.couldRotateCredentials(model, undefined))
570
+ return undefined;
571
+ const sources = await this.credentialRotationSources(model, undefined);
572
+ if (!sources)
573
+ return undefined;
574
+ const { rotation } = await this.loadCredentialPool();
575
+ const { selectSessionSlot } = await import("./credential-pool/session-slot.js");
576
+ const slots = await rotation.listRotationSlots(sources, {
577
+ acquireLeases: false,
578
+ modelId: this.getCompatibilityRequestConfig(model).upstreamModelId ?? model.id,
579
+ });
580
+ const pooled = sources.credential;
581
+ const slot = selectSessionSlot(slots, {
582
+ affinityKey: sessionId,
583
+ useAffinity: sources.policy?.affinity !== false,
584
+ hasher: rotation.sha256SlotHasher,
585
+ ...(pooled?.order === undefined ? {} : { order: pooled.order }),
586
+ }, Date.now());
587
+ if (!slot)
588
+ return undefined;
589
+ return slot.lane === "env" ? { apiKey: slot.envKey } : { slotName: slot.name };
590
+ }
553
591
  enqueueCredentialOperation(providerId, signal, task) {
554
592
  const previous = this.credentialOperations.get(providerId) ?? Promise.resolve();
555
593
  let markStarted;
@@ -946,7 +984,7 @@ export class ModelRuntime {
946
984
  return this.enqueueCredentialOperation(providerId, signal, async () => {
947
985
  const store = this.credentials.backingStore;
948
986
  const before = await store.read(providerId, { signal });
949
- const credential = await this.models.login(providerId, type, { ...interaction, signal }, options);
987
+ const credential = await this.models.login(providerId, type, { ...interaction, signal }, { ...options, appName: APP_NAME });
950
988
  if (this.poolStatePath !== undefined) {
951
989
  const { retireLoginSlotHealth } = await import("./credential-pool/login-health.js");
952
990
  await retireLoginSlotHealth(this.poolStatePath, providerId, before, await store.read(providerId, { signal }));
@@ -1,5 +1,6 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import { type Api, type AssistantMessage, type Model, type ProviderDiagnostic, type ProviderDiagnosticCategory } from "@earendil-works/pi-ai";
3
+ import { type TransportTrustFailure } from "../engine-trust/transport-failure.ts";
3
4
  import { type FallbackModelLookup } from "./retry-fallback/chains.ts";
4
5
  /**
5
6
  * `request_refused`: an `auth` failure on a credential that served a response moments before, so the
@@ -33,6 +34,11 @@ export interface ProviderFailure {
33
34
  readonly resetsAt?: number;
34
35
  readonly reason: string;
35
36
  readonly action: string;
37
+ /**
38
+ * Present when the request failed TLS chain verification while the engine runs on a fallback trust
39
+ * generation: the fallback reason and its repair action id (senpi#5244).
40
+ */
41
+ readonly trust?: TransportTrustFailure;
36
42
  }
37
43
  /** An `auth` failure on a credential that served a response moments before: the request was refused, not the key. */
38
44
  export declare function isRequestLevelRefusal(message: AssistantMessage): boolean;
@@ -1,4 +1,5 @@
1
1
  import { PROVIDER_NOT_CONFIGURED_PREFIX, sanitizeProviderDiagnostic, stripTurnRetrySuppressionPrefix, } from "@earendil-works/pi-ai";
2
+ import { transportTrustFailure } from "../engine-trust/transport-failure.js";
2
3
  import { parseFallbackSelector } from "./retry-fallback/chains.js";
3
4
  const ACTIONS = {
4
5
  auth: { reason: "the provider rejected the credentials", action: "re-authenticate the provider" },
@@ -82,11 +83,14 @@ export function providerFailureOf(messages, selected) {
82
83
  const last = messages.findLast((message) => message.role === "assistant");
83
84
  if (last === undefined || last.role !== "assistant" || last.stopReason !== "error")
84
85
  return undefined;
85
- const failure = notConfiguredFailure(last) ??
86
- describeProviderFailure(sanitizeProviderDiagnostic(last.providerDiagnostic), last.provider, {
86
+ const diagnostic = sanitizeProviderDiagnostic(last.providerDiagnostic);
87
+ const described = notConfiguredFailure(last) ??
88
+ describeProviderFailure(diagnostic, last.provider, {
87
89
  model: last.model,
88
90
  requestRefused: isRequestLevelRefusal(last),
89
91
  });
92
+ const trust = transportTrustFailure(diagnostic);
93
+ const failure = trust === undefined ? described : { ...described, trust };
90
94
  if (selected === undefined || (selected.provider === last.provider && selected.model === last.model)) {
91
95
  return failure;
92
96
  }
@@ -14,6 +14,8 @@ export interface ActiveFallbackState {
14
14
  pinnedByBilling: boolean;
15
15
  /** Derived OR of the two provenance flags - the only field consumers read. */
16
16
  pinned: boolean;
17
+ /** The original provider's login id when the episode began; a sign-in moves it and releases a billing pin (senpi#4099). */
18
+ recoveryRevision?: string;
17
19
  }
18
20
  export type FallbackReason = "transient" | "refusal" | "hard-error" | "billing";
19
21
  /** Absent: the original recovered after its cooldown. `fallback-unusable`: the fallback itself could not serve. */
@@ -64,5 +66,10 @@ export interface RetryFallbackControllerDeps {
64
66
  thinkingLevel?: ThinkingLevel;
65
67
  } | undefined;
66
68
  isAuthAvailable(provider: string): boolean;
69
+ /**
70
+ * The `loginId` the last sign-in stored for `provider` ("" when none did), never key material;
71
+ * `undefined` when it cannot be read. Only a sign-in writes a new id; a token refresh keeps it or drops it to "" (senpi#4099).
72
+ */
73
+ recoveryRevision?(provider: string): Promise<string | undefined>;
67
74
  }
68
75
  //# sourceMappingURL=controller-types.d.ts.map