@code-yeongyu/senpi 2026.9.15 → 2026.9.16-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 (333) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +1 -1
  3. package/dist/core/agent-session.d.ts +28 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +111 -13
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/compaction/compaction.d.ts.map +1 -1
  8. package/dist/core/compaction/compaction.js +4 -2
  9. package/dist/core/compaction/compaction.js.map +1 -1
  10. package/dist/core/compaction/stream-watchdog.d.ts +56 -2
  11. package/dist/core/compaction/stream-watchdog.d.ts.map +1 -1
  12. package/dist/core/compaction/stream-watchdog.js +93 -15
  13. package/dist/core/compaction/stream-watchdog.js.map +1 -1
  14. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts +6 -0
  15. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
  16. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +17 -0
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -1
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +31 -0
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts +2 -0
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js +16 -0
  25. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js.map +1 -1
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js +19 -8
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js.map +1 -1
  29. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +2 -1
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +13 -1
  33. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +66 -2
  35. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/compaction/index.js +16 -10
  38. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  39. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/speculative-summary.js +4 -2
  41. package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/speculative.d.ts +8 -1
  43. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/speculative.js +31 -7
  45. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  46. package/dist/core/extensions/builtin/compaction/transient-failure.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/compaction/transient-failure.js +6 -3
  48. package/dist/core/extensions/builtin/compaction/transient-failure.js.map +1 -1
  49. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/config-reload/index.js +25 -8
  51. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  52. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +4 -5
  53. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/config-reload/watch-engine.js +14 -18
  55. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -1
  56. package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/config-reload/watch-event-source.js +32 -9
  58. package/dist/core/extensions/builtin/config-reload/watch-event-source.js.map +1 -1
  59. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts +23 -0
  60. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts.map +1 -0
  61. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js +48 -0
  62. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js.map +1 -0
  63. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts +13 -0
  64. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts.map +1 -0
  65. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js +24 -0
  66. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js.map +1 -0
  67. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts +1 -0
  68. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js +7 -1
  70. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js.map +1 -1
  71. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts +2 -0
  72. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +11 -9
  74. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
  75. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts +18 -0
  76. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js +61 -0
  78. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js.map +1 -0
  79. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts +4 -1
  80. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts.map +1 -1
  81. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js +1 -44
  82. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js.map +1 -1
  83. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +22 -0
  84. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
  85. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +1 -1
  86. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
  87. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.d.ts.map +1 -1
  88. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js +2 -13
  89. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js.map +1 -1
  90. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts +3 -0
  91. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts.map +1 -0
  92. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js +25 -0
  93. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js.map +1 -0
  94. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts +9 -0
  95. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts.map +1 -0
  96. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js +33 -0
  97. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js.map +1 -0
  98. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts +6 -0
  99. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts.map +1 -0
  100. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js +22 -0
  101. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js.map +1 -0
  102. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  103. package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -35
  104. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  105. package/dist/core/extensions/kernel-tools-context.d.ts +13 -0
  106. package/dist/core/extensions/kernel-tools-context.d.ts.map +1 -0
  107. package/dist/core/extensions/kernel-tools-context.js +3 -0
  108. package/dist/core/extensions/kernel-tools-context.js.map +1 -0
  109. package/dist/core/extensions/runner.d.ts +19 -0
  110. package/dist/core/extensions/runner.d.ts.map +1 -1
  111. package/dist/core/extensions/runner.js +103 -1
  112. package/dist/core/extensions/runner.js.map +1 -1
  113. package/dist/core/extensions/types.d.ts +21 -0
  114. package/dist/core/extensions/types.d.ts.map +1 -1
  115. package/dist/core/extensions/types.js.map +1 -1
  116. package/dist/core/keybindings.d.ts +5 -0
  117. package/dist/core/keybindings.d.ts.map +1 -1
  118. package/dist/core/keybindings.js +1 -0
  119. package/dist/core/keybindings.js.map +1 -1
  120. package/dist/core/resource-loader.d.ts.map +1 -1
  121. package/dist/core/resource-loader.js +9 -17
  122. package/dist/core/resource-loader.js.map +1 -1
  123. package/dist/core/retry-fallback/settings.d.ts +3 -0
  124. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  125. package/dist/core/retry-fallback/settings.js.map +1 -1
  126. package/dist/core/sdk.d.ts.map +1 -1
  127. package/dist/core/sdk.js +1 -0
  128. package/dist/core/sdk.js.map +1 -1
  129. package/dist/core/session-manager.d.ts +30 -1
  130. package/dist/core/session-manager.d.ts.map +1 -1
  131. package/dist/core/session-manager.js +92 -5
  132. package/dist/core/session-manager.js.map +1 -1
  133. package/dist/core/session-resident-store.d.ts +33 -7
  134. package/dist/core/session-resident-store.d.ts.map +1 -1
  135. package/dist/core/session-resident-store.js +163 -14
  136. package/dist/core/session-resident-store.js.map +1 -1
  137. package/dist/core/session-write-reservation.d.ts +6 -0
  138. package/dist/core/session-write-reservation.d.ts.map +1 -1
  139. package/dist/core/session-write-reservation.js +19 -0
  140. package/dist/core/session-write-reservation.js.map +1 -1
  141. package/dist/core/settings-manager.d.ts +35 -1
  142. package/dist/core/settings-manager.d.ts.map +1 -1
  143. package/dist/core/settings-manager.js +62 -0
  144. package/dist/core/settings-manager.js.map +1 -1
  145. package/dist/core/slash-commands.d.ts.map +1 -1
  146. package/dist/core/slash-commands.js +2 -1
  147. package/dist/core/slash-commands.js.map +1 -1
  148. package/dist/core/tools/index.d.ts +1 -1
  149. package/dist/core/tools/index.d.ts.map +1 -1
  150. package/dist/core/tools/index.js +4 -3
  151. package/dist/core/tools/index.js.map +1 -1
  152. package/dist/core/tools/read.d.ts +3 -1
  153. package/dist/core/tools/read.d.ts.map +1 -1
  154. package/dist/core/tools/read.js +16 -2
  155. package/dist/core/tools/read.js.map +1 -1
  156. package/dist/index.d.ts +1 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +1 -0
  159. package/dist/index.js.map +1 -1
  160. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/assistant-render-descriptors.js +12 -5
  162. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  163. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  164. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/extension-input.js +5 -0
  166. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +19 -2
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +69 -17
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
  172. package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
  173. package/dist/modes/interactive/working-status.d.ts +3 -2
  174. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  175. package/dist/modes/interactive/working-status.js +17 -4
  176. package/dist/modes/interactive/working-status.js.map +1 -1
  177. package/dist/modes/print-mode.d.ts +1 -1
  178. package/dist/modes/print-mode.d.ts.map +1 -1
  179. package/dist/modes/print-mode.js +5 -1
  180. package/dist/modes/print-mode.js.map +1 -1
  181. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  182. package/dist/modes/rpc/host-watchdog.js +5 -24
  183. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  184. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  185. package/dist/modes/rpc/rpc-mode.js +3 -8
  186. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  187. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  188. package/dist/modes/rpc/session-command-router.js +3 -0
  189. package/dist/modes/rpc/session-command-router.js.map +1 -1
  190. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  191. package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
  192. package/dist/modes/rpc/session-worker-client.js +17 -6
  193. package/dist/modes/rpc/session-worker-client.js.map +1 -1
  194. package/dist/modes/rpc/shutdown.d.ts +3 -0
  195. package/dist/modes/rpc/shutdown.d.ts.map +1 -0
  196. package/dist/modes/rpc/shutdown.js +17 -0
  197. package/dist/modes/rpc/shutdown.js.map +1 -0
  198. package/docs/extensions.md +5 -2
  199. package/docs/keybindings.md +1 -0
  200. package/docs/session-format.md +1 -1
  201. package/docs/sessions.md +3 -3
  202. package/docs/settings.md +31 -1
  203. package/docs/usage.md +1 -1
  204. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +50 -0
  205. package/node_modules/@code-yeongyu/senpi-codemode/README.md +24 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  207. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/http-server.ts +7 -1
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/kernel-tools-protocol.ts +85 -0
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +6 -0
  210. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +30 -11
  211. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +11 -0
  212. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  213. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +46 -0
  214. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +1 -1
  215. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +58 -10
  216. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +12 -0
  217. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.d.ts +13 -0
  218. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.js +7 -0
  219. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.d.ts +10 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.js +12 -0
  221. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.js +22 -0
  222. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.ts +26 -0
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-host.ts +134 -0
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-naming.js +28 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.d.ts +5 -0
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.js +255 -0
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-pump.js +105 -0
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.d.ts +27 -0
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.js +127 -0
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-schema.js +69 -0
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-types.ts +42 -0
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +2 -0
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +26 -3
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +20 -8
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +6 -1
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +9 -7
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/workpool.js +37 -0
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/kernel-tools-unavailable.ts +5 -0
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +13 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +57 -3
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +13 -2
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/workpool.rb +38 -0
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +13 -0
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +9 -1
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +4 -2
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +31 -3
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +36 -71
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image-resize.ts +52 -0
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +37 -72
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/managed-cell.ts +78 -0
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +30 -5
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +52 -30
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/terminal-snapshot-store.ts +35 -0
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/tool-result-marshal.ts +23 -0
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  256. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  257. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +30 -3
  258. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  259. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +5 -0
  260. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  261. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +2 -0
  262. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +51 -12
  265. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +3 -0
  267. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -1
  268. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +18 -3
  269. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts +4 -0
  271. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts.map +1 -0
  272. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js +268 -0
  273. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js.map +1 -0
  274. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts +18 -0
  275. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts.map +1 -0
  276. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js +69 -0
  277. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js.map +1 -0
  278. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts +27 -0
  279. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts.map +1 -0
  280. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js +118 -0
  281. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js.map +1 -0
  282. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts +21 -0
  283. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts.map +1 -0
  284. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js +24 -0
  285. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js.map +1 -0
  286. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts +12 -0
  287. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts.map +1 -0
  288. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js +92 -0
  289. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js.map +1 -0
  290. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts +47 -0
  291. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts.map +1 -0
  292. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js +8 -0
  293. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js.map +1 -0
  294. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts +52 -0
  295. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts.map +1 -0
  296. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js +155 -0
  297. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js.map +1 -0
  298. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +4 -0
  299. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  300. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +3 -0
  301. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  302. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts +101 -0
  303. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts.map +1 -0
  304. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js +178 -0
  305. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js.map +1 -0
  306. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +9 -0
  307. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  308. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  309. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  310. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  311. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  313. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  316. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  319. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts +15 -0
  322. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts.map +1 -0
  323. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js +15 -0
  324. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js.map +1 -0
  325. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +24 -0
  326. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  327. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +92 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  329. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  330. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  331. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  332. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  333. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"stream-watchdog.js","sourceRoot":"","sources":["../../../src/core/compaction/stream-watchdog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Idle watchdog for compaction summarization streams.\n *\n * A provider connection can stall — open but silent — for far longer than any\n * user will wait, and compaction previously had no bound at all: the session\n * sat on \"Compacting…\" until ESC aborted it. The agent loop's main-turn\n * reader already has this shape of protection (`StreamIdleTimeoutError` in\n * packages/agent); this brings the same guarantee to summarization requests.\n */\n\nexport class StreamIdleTimeoutError extends Error {\n\treadonly idleTimeoutMs: number;\n\tconstructor(idleTimeoutMs: number) {\n\t\tsuper(`Summarization stream stalled: no provider events for ${idleTimeoutMs}ms; treating the request as dead`);\n\t\tthis.name = \"StreamIdleTimeoutError\";\n\t\tthis.idleTimeoutMs = idleTimeoutMs;\n\t}\n}\n\n/**\n * A stream that keeps trickling events never trips the idle watchdog, yet the\n * summarization it feeds is serialized on the session's agent-event queue: a\n * slow-but-alive request holds tool results at the batch barrier and keeps typed\n * input queued until the user aborts. The wall-clock budget bounds that class.\n */\nexport class StreamDurationBudgetError extends Error {\n\treadonly maxDurationMs: number;\n\tconstructor(maxDurationMs: number) {\n\t\tsuper(\n\t\t\t`Summarization stream exceeded its ${maxDurationMs}ms wall-clock budget; treating the request as too slow to keep the session waiting`,\n\t\t);\n\t\tthis.name = \"StreamDurationBudgetError\";\n\t\tthis.maxDurationMs = maxDurationMs;\n\t}\n}\n\n/** Matches the agent stream idle-timeout default (`httpIdleTimeoutMs`). */\nexport const DEFAULT_SUMMARIZATION_IDLE_TIMEOUT_MS = 300_000;\n\n/**\n * Total time one summarization attempt may hold the session. Well above healthy\n * summarizations (tens of seconds) and below the idle budget, so a live-but-slow\n * provider fails fast enough to keep the session interactive. Retries apply this\n * budget per attempt.\n */\nexport const DEFAULT_SUMMARIZATION_MAX_DURATION_MS = 120_000;\n\n/**\n * How much the wall-clock budget grows per estimated input token.\n *\n * The 120s floor covers healthy summaries. Large sessions summarize hundreds of\n * thousands of tokens: a 257k-token input already exceeds 120s on slower\n * providers while still streaming (#1068), so the budget scales with the amount\n * being summarized. 2ms/token assumes a worst-case sustained throughput of ~500\n * tokens/second; faster providers simply finish well inside the budget.\n */\nexport const SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS = 2;\n\n/**\n * Absolute ceiling for one summarization attempt. The wall-clock budget exists\n * to stop a slow-but-alive stream from holding the session forever; it must stay\n * a bound, so size-scaled budgets are clamped here instead of growing linearly\n * without limit.\n */\nexport const SUMMARIZATION_MAX_DURATION_CAP_MS = 1_800_000;\n\n/**\n * Total time one summarization attempt may hold the session, sized to its input.\n *\n * The budget never shrinks below {@link DEFAULT_SUMMARIZATION_MAX_DURATION_MS};\n * inputs small enough that `2ms/token` stays under that floor keep the exact\n * 120s contract. Above ~60k estimated tokens the budget grows by\n * {@link SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS} per token and is clamped to\n * {@link SUMMARIZATION_MAX_DURATION_CAP_MS}. An explicit positive `overrideMs`\n * (e.g. the `compaction.summarizationMaxDurationMs` setting) replaces the\n * computed budget; non-finite and non-positive values are ignored.\n */\nexport function summarizationMaxDurationMs(estimatedInputTokens: number, overrideMs?: number): number {\n\tif (overrideMs !== undefined && Number.isFinite(overrideMs) && overrideMs > 0) {\n\t\treturn Math.min(SUMMARIZATION_MAX_DURATION_CAP_MS, overrideMs);\n\t}\n\tconst scaled = estimatedInputTokens * SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS;\n\treturn Math.min(SUMMARIZATION_MAX_DURATION_CAP_MS, Math.max(DEFAULT_SUMMARIZATION_MAX_DURATION_MS, scaled));\n}\n\nexport interface ConsumeStreamWithIdleTimeoutOptions<T> {\n\t/** Silence budget per read; the timer resets on every event. */\n\treadonly idleTimeoutMs: number;\n\t/** Total wall-clock budget for the whole stream; omit to leave it unbounded. */\n\treadonly maxDurationMs?: number;\n\t/** Tear down the underlying request (abort the request-local controller). */\n\treadonly abort: () => void;\n\treadonly onEvent?: (event: T) => void;\n\t/** Caller cancellation; an abort here ends the wait without an idle error. */\n\treadonly signal?: AbortSignal;\n}\n\nconst IDLE_TRIP = \"idle-trip\" as const;\nconst BUDGET_TRIP = \"budget-trip\" as const;\nconst CALLER_ABORTED = \"caller-aborted\" as const;\n\n/**\n * Drain an event stream, failing with {@link StreamIdleTimeoutError} when no\n * event arrives within `idleTimeoutMs`. Caller aborts propagate as the\n * stream's own abort outcome, never masked as an idle timeout.\n */\nexport async function consumeStreamWithIdleTimeout<T>(\n\tstream: AsyncIterable<T> | PromiseLike<AsyncIterable<T>>,\n\toptions: ConsumeStreamWithIdleTimeoutOptions<T>,\n): Promise<void> {\n\tconst { idleTimeoutMs, maxDurationMs, abort, onEvent, signal } = options;\n\tlet iterator: AsyncIterator<T> | undefined;\n\tlet removeAbortListener: (() => void) | undefined;\n\tlet callerAbortPromise: Promise<typeof CALLER_ABORTED> | undefined;\n\tif (signal?.aborted) {\n\t\treturn;\n\t}\n\t// One absolute deadline for the whole stream, not a per-read budget. Created\n\t// only after the already-aborted early return so no timer is ever leaked.\n\tlet budgetPromise: Promise<typeof BUDGET_TRIP> | undefined;\n\tlet budgetTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet budgetMs = 0;\n\tif (maxDurationMs !== undefined) {\n\t\tbudgetMs = maxDurationMs;\n\t\tconst { promise, resolve } = Promise.withResolvers<typeof BUDGET_TRIP>();\n\t\tbudgetTimer = setTimeout(() => resolve(BUDGET_TRIP), budgetMs);\n\t\tbudgetTimer.unref?.();\n\t\tbudgetPromise = promise;\n\t}\n\tif (signal !== undefined) {\n\t\tconst { promise, resolve } = Promise.withResolvers<typeof CALLER_ABORTED>();\n\t\tconst onAbort = () => resolve(CALLER_ABORTED);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tremoveAbortListener = () => signal.removeEventListener(\"abort\", onAbort);\n\t\tcallerAbortPromise = promise;\n\t}\n\ttry {\n\t\tlet resolvedStream: AsyncIterable<T>;\n\t\tif (Symbol.asyncIterator in stream) {\n\t\t\tresolvedStream = stream;\n\t\t} else {\n\t\t\tconst streamContenders: Array<Promise<AsyncIterable<T> | typeof BUDGET_TRIP | typeof CALLER_ABORTED>> = [\n\t\t\t\tPromise.resolve(stream),\n\t\t\t];\n\t\t\tif (callerAbortPromise) streamContenders.push(callerAbortPromise);\n\t\t\tif (budgetPromise) streamContenders.push(budgetPromise);\n\t\t\tconst resolution = await Promise.race(streamContenders);\n\t\t\tif (resolution === BUDGET_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tthrow new StreamDurationBudgetError(budgetMs);\n\t\t\t}\n\t\t\tif (resolution === CALLER_ABORTED) return;\n\t\t\tresolvedStream = resolution;\n\t\t}\n\t\titerator = resolvedStream[Symbol.asyncIterator]();\n\n\t\twhile (true) {\n\t\t\tconst { promise: idlePromise, resolve: resolveIdle } = Promise.withResolvers<typeof IDLE_TRIP>();\n\t\t\tconst timer = setTimeout(() => resolveIdle(IDLE_TRIP), idleTimeoutMs);\n\t\t\ttimer.unref?.();\n\t\t\tconst contenders: Array<\n\t\t\t\tPromise<IteratorResult<T> | typeof IDLE_TRIP | typeof BUDGET_TRIP | typeof CALLER_ABORTED>\n\t\t\t> = [iterator.next(), idlePromise];\n\t\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\t\tif (budgetPromise) contenders.push(budgetPromise);\n\t\t\tlet result: IteratorResult<T> | typeof IDLE_TRIP | typeof BUDGET_TRIP | typeof CALLER_ABORTED;\n\t\t\ttry {\n\t\t\t\tresult = await Promise.race(contenders);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (result === IDLE_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\tthrow new StreamIdleTimeoutError(idleTimeoutMs);\n\t\t\t}\n\t\t\tif (result === BUDGET_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\tthrow new StreamDurationBudgetError(budgetMs);\n\t\t\t}\n\t\t\tif (result === CALLER_ABORTED) {\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (result.done) return;\n\t\t\tonEvent?.(result.value);\n\t\t}\n\t} finally {\n\t\tremoveAbortListener?.();\n\t\tif (budgetTimer !== undefined) clearTimeout(budgetTimer);\n\t}\n}\n"]}
1
+ {"version":3,"file":"stream-watchdog.js","sourceRoot":"","sources":["../../../src/core/compaction/stream-watchdog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Idle watchdog for compaction summarization streams.\n *\n * A provider connection can stall — open but silent — for far longer than any\n * user will wait, and compaction previously had no bound at all: the session\n * sat on \"Compacting…\" until ESC aborted it. The agent loop's main-turn\n * reader already has this shape of protection (`StreamIdleTimeoutError` in\n * packages/agent); this brings the same guarantee to summarization requests.\n */\n\nexport class StreamIdleTimeoutError extends Error {\n\treadonly idleTimeoutMs: number;\n\tconstructor(idleTimeoutMs: number) {\n\t\tsuper(`Summarization stream stalled: no provider events for ${idleTimeoutMs}ms; treating the request as dead`);\n\t\tthis.name = \"StreamIdleTimeoutError\";\n\t\tthis.idleTimeoutMs = idleTimeoutMs;\n\t}\n}\n\n/**\n * A stream that keeps trickling events never trips the idle watchdog, yet the\n * summarization it feeds is serialized on the session's agent-event queue: a\n * slow-but-alive request holds tool results at the batch barrier and keeps typed\n * input queued until the user aborts. The wall-clock budget bounds that class.\n */\nexport class StreamDurationBudgetError extends Error {\n\treadonly maxDurationMs: number;\n\tconstructor(maxDurationMs: number) {\n\t\tsuper(\n\t\t\t`Summarization stream exceeded its ${maxDurationMs}ms wall-clock budget; treating the request as too slow to keep the session waiting`,\n\t\t);\n\t\tthis.name = \"StreamDurationBudgetError\";\n\t\tthis.maxDurationMs = maxDurationMs;\n\t}\n}\n\n/** Matches the agent stream idle-timeout default (`httpIdleTimeoutMs`). */\nexport const DEFAULT_SUMMARIZATION_IDLE_TIMEOUT_MS = 300_000;\n\n/**\n * Total time one summarization attempt may hold the session. Well above healthy\n * summarizations (tens of seconds) and below the idle budget, so a live-but-slow\n * provider fails fast enough to keep the session interactive. Retries apply this\n * budget per attempt.\n */\nexport const DEFAULT_SUMMARIZATION_MAX_DURATION_MS = 120_000;\n\n/**\n * How much the wall-clock budget grows per estimated input token.\n *\n * The 120s floor covers healthy summaries. Large sessions summarize hundreds of\n * thousands of tokens: a 257k-token input already exceeds 120s on slower\n * providers while still streaming (#1068), so the budget scales with the amount\n * being summarized. 2ms/token assumes a worst-case sustained throughput of ~500\n * tokens/second; faster providers simply finish well inside the budget.\n */\nexport const SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS = 2;\n\n/**\n * Absolute ceiling for one summarization attempt. The wall-clock budget exists\n * to stop a slow-but-alive stream from holding the session forever; it must stay\n * a bound, so size-scaled budgets are clamped here instead of growing linearly\n * without limit.\n */\nexport const SUMMARIZATION_MAX_DURATION_CAP_MS = 1_800_000;\n\n/**\n * Total wall clock ONE compaction may hold the session across every attempt,\n * retry and overflow shrink.\n *\n * The per-attempt budget is deliberately proportional to the input, so a large\n * session legally licenses a 690s attempt (345k tokens) or the full 30-minute\n * ceiling (900k tokens), and each retry re-arms that budget from scratch: the\n * user's wait grew with the very thing that made it slow, without bound (#1741).\n * This cap is the session-health bound the per-attempt budget cannot be: it\n * never scales with the input, and every attempt of one compaction shares it.\n */\nexport const SUMMARIZATION_TOTAL_BUDGET_MS = 900_000;\n\n/**\n * Total budget for one compaction. Size never raises it; only an explicit\n * `compaction.summarizationMaxDurationMs` override does, because an operator who\n * deliberately allows a longer single attempt must not have that attempt cut\n * short by the total. Clamped to {@link SUMMARIZATION_MAX_DURATION_CAP_MS}.\n */\nexport function summarizationTotalBudgetMs(attemptOverrideMs?: number): number {\n\tconst override =\n\t\tattemptOverrideMs !== undefined && Number.isFinite(attemptOverrideMs) && attemptOverrideMs > 0\n\t\t\t? Math.min(SUMMARIZATION_MAX_DURATION_CAP_MS, attemptOverrideMs)\n\t\t\t: 0;\n\treturn Math.max(SUMMARIZATION_TOTAL_BUDGET_MS, override);\n}\n\n/**\n * One compaction outlived {@link SUMMARIZATION_TOTAL_BUDGET_MS}. Distinct from\n * {@link StreamDurationBudgetError}, which bounds a single attempt: this one says\n * no further attempt may start, so recovery must come from the deterministic\n * fallback rather than another provider request.\n */\nexport class SummarizationTotalBudgetError extends Error {\n\treadonly totalBudgetMs: number;\n\tconstructor(totalBudgetMs: number) {\n\t\tsuper(\n\t\t\t`Compaction exceeded its ${totalBudgetMs}ms total wall-clock budget across every summarization attempt and retry`,\n\t\t);\n\t\tthis.name = \"SummarizationTotalBudgetError\";\n\t\tthis.totalBudgetMs = totalBudgetMs;\n\t}\n}\n\nexport interface SummarizationDeadline {\n\treadonly totalBudgetMs: number;\n\t/** Time left before the whole compaction is out of budget; never negative. */\n\tremainingMs(): number;\n\t/**\n\t * Clamp one attempt's wall-clock budget to what the compaction has left, so a\n\t * retry started near the deadline cannot re-arm a full attempt budget. Throws\n\t * {@link SummarizationTotalBudgetError} once nothing is left.\n\t */\n\tattemptBudgetMs(requestedMs: number): number;\n}\n\nexport function createSummarizationDeadline(\n\ttotalBudgetMs: number,\n\tnow: () => number = Date.now,\n): SummarizationDeadline {\n\tconst startedMs = now();\n\tconst remainingMs = (): number => Math.max(0, totalBudgetMs - (now() - startedMs));\n\treturn {\n\t\ttotalBudgetMs,\n\t\tremainingMs,\n\t\tattemptBudgetMs: (requestedMs: number): number => {\n\t\t\tconst remaining = remainingMs();\n\t\t\tif (remaining <= 0) throw new SummarizationTotalBudgetError(totalBudgetMs);\n\t\t\treturn Math.min(requestedMs, remaining);\n\t\t},\n\t};\n}\n\n/**\n * Total time one summarization attempt may hold the session, sized to its input.\n *\n * The budget never shrinks below {@link DEFAULT_SUMMARIZATION_MAX_DURATION_MS};\n * inputs small enough that `2ms/token` stays under that floor keep the exact\n * 120s contract. Above ~60k estimated tokens the budget grows by\n * {@link SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS} per token and is clamped to\n * {@link SUMMARIZATION_MAX_DURATION_CAP_MS}. An explicit positive `overrideMs`\n * (e.g. the `compaction.summarizationMaxDurationMs` setting) replaces the\n * computed budget; non-finite and non-positive values are ignored.\n */\nexport function summarizationMaxDurationMs(estimatedInputTokens: number, overrideMs?: number): number {\n\tif (overrideMs !== undefined && Number.isFinite(overrideMs) && overrideMs > 0) {\n\t\treturn Math.min(SUMMARIZATION_MAX_DURATION_CAP_MS, overrideMs);\n\t}\n\tconst scaled = estimatedInputTokens * SUMMARIZATION_MAX_DURATION_PER_TOKEN_MS;\n\treturn Math.min(SUMMARIZATION_MAX_DURATION_CAP_MS, Math.max(DEFAULT_SUMMARIZATION_MAX_DURATION_MS, scaled));\n}\n\nexport interface ConsumeStreamWithIdleTimeoutOptions<T, R = void> {\n\t/** Silence budget per read; the timer resets on every event. */\n\treadonly idleTimeoutMs: number;\n\t/** Total wall-clock budget for the whole stream; omit to leave it unbounded. */\n\treadonly maxDurationMs?: number;\n\t/** Tear down the underlying request (abort the request-local controller). */\n\treadonly abort: () => void;\n\treadonly onEvent?: (event: T) => void;\n\t/** Caller cancellation; an abort here ends the wait without an idle error. */\n\treadonly signal?: AbortSignal;\n\t/**\n\t * Final settlement of the stream (its `result()`), awaited under the SAME\n\t * timers as iteration. A provider whose iterator ends without pushing a\n\t * terminal `done`/`error` event leaves `result()` pending forever; settling it\n\t * after the watchdog's timers were cleared parked compaction with no timer\n\t * armed at all (#1741).\n\t */\n\treadonly settle?: () => Promise<R>;\n}\n\nconst IDLE_TRIP = \"idle-trip\" as const;\nconst BUDGET_TRIP = \"budget-trip\" as const;\nconst CALLER_ABORTED = \"caller-aborted\" as const;\n\n/**\n * Drain an event stream, failing with {@link StreamIdleTimeoutError} when no\n * event arrives within `idleTimeoutMs`. Caller aborts propagate as the\n * stream's own abort outcome, never masked as an idle timeout.\n */\nexport function consumeStreamWithIdleTimeout<T>(\n\tstream: AsyncIterable<T> | PromiseLike<AsyncIterable<T>>,\n\toptions: ConsumeStreamWithIdleTimeoutOptions<T, void> & { settle?: undefined },\n): Promise<void>;\nexport function consumeStreamWithIdleTimeout<T, R>(\n\tstream: AsyncIterable<T> | PromiseLike<AsyncIterable<T>>,\n\toptions: ConsumeStreamWithIdleTimeoutOptions<T, R> & { settle: () => Promise<R> },\n): Promise<R>;\nexport async function consumeStreamWithIdleTimeout<T, R>(\n\tstream: AsyncIterable<T> | PromiseLike<AsyncIterable<T>>,\n\toptions: ConsumeStreamWithIdleTimeoutOptions<T, R>,\n): Promise<R | undefined> {\n\tconst { idleTimeoutMs, maxDurationMs, abort, onEvent, signal, settle } = options;\n\tlet iterator: AsyncIterator<T> | undefined;\n\tlet removeAbortListener: (() => void) | undefined;\n\tlet callerAbortPromise: Promise<typeof CALLER_ABORTED> | undefined;\n\t// One absolute deadline for the whole stream, not a per-read budget.\n\tlet budgetPromise: Promise<typeof BUDGET_TRIP> | undefined;\n\tlet budgetTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet budgetMs = 0;\n\tif (maxDurationMs !== undefined) {\n\t\tbudgetMs = maxDurationMs;\n\t\tconst { promise, resolve } = Promise.withResolvers<typeof BUDGET_TRIP>();\n\t\tbudgetTimer = setTimeout(() => resolve(BUDGET_TRIP), budgetMs);\n\t\tbudgetTimer.unref?.();\n\t\tbudgetPromise = promise;\n\t}\n\tif (signal !== undefined && !signal.aborted) {\n\t\tconst { promise, resolve } = Promise.withResolvers<typeof CALLER_ABORTED>();\n\t\tconst onAbort = () => resolve(CALLER_ABORTED);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tremoveAbortListener = () => signal.removeEventListener(\"abort\", onAbort);\n\t\tcallerAbortPromise = promise;\n\t}\n\t// Settle the stream under the timers this call already armed. Every exit that\n\t// is not a thrown watchdog error goes through here, so `result()` can never be\n\t// awaited with no deadline in force.\n\tconst settleUnderWatchdogs = async (): Promise<R | undefined> => {\n\t\tif (!settle) return undefined;\n\t\tconst { promise: idlePromise, resolve: resolveIdle } = Promise.withResolvers<typeof IDLE_TRIP>();\n\t\tconst timer = setTimeout(() => resolveIdle(IDLE_TRIP), idleTimeoutMs);\n\t\ttimer.unref?.();\n\t\tconst contenders: Array<Promise<{ settled: R } | typeof IDLE_TRIP | typeof BUDGET_TRIP>> = [\n\t\t\tsettle().then((value) => ({ settled: value })),\n\t\t\tidlePromise,\n\t\t];\n\t\tif (budgetPromise) contenders.push(budgetPromise);\n\t\tlet outcome: { settled: R } | typeof IDLE_TRIP | typeof BUDGET_TRIP;\n\t\ttry {\n\t\t\toutcome = await Promise.race(contenders);\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tif (outcome === IDLE_TRIP) {\n\t\t\tabort();\n\t\t\tthrow new StreamIdleTimeoutError(idleTimeoutMs);\n\t\t}\n\t\tif (outcome === BUDGET_TRIP) {\n\t\t\tabort();\n\t\t\tthrow new StreamDurationBudgetError(budgetMs);\n\t\t}\n\t\treturn outcome.settled;\n\t};\n\ttry {\n\t\t// A caller that already cancelled still settles its stream - the terminal\n\t\t// aborted message is what callers return - but under the same watchdogs.\n\t\tif (signal?.aborted) return await settleUnderWatchdogs();\n\t\tlet resolvedStream: AsyncIterable<T>;\n\t\tif (Symbol.asyncIterator in stream) {\n\t\t\tresolvedStream = stream;\n\t\t} else {\n\t\t\tconst streamContenders: Array<Promise<AsyncIterable<T> | typeof BUDGET_TRIP | typeof CALLER_ABORTED>> = [\n\t\t\t\tPromise.resolve(stream),\n\t\t\t];\n\t\t\tif (callerAbortPromise) streamContenders.push(callerAbortPromise);\n\t\t\tif (budgetPromise) streamContenders.push(budgetPromise);\n\t\t\tconst resolution = await Promise.race(streamContenders);\n\t\t\tif (resolution === BUDGET_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tthrow new StreamDurationBudgetError(budgetMs);\n\t\t\t}\n\t\t\tif (resolution === CALLER_ABORTED) return await settleUnderWatchdogs();\n\t\t\tresolvedStream = resolution;\n\t\t}\n\t\titerator = resolvedStream[Symbol.asyncIterator]();\n\n\t\twhile (true) {\n\t\t\tconst { promise: idlePromise, resolve: resolveIdle } = Promise.withResolvers<typeof IDLE_TRIP>();\n\t\t\tconst timer = setTimeout(() => resolveIdle(IDLE_TRIP), idleTimeoutMs);\n\t\t\ttimer.unref?.();\n\t\t\tconst contenders: Array<\n\t\t\t\tPromise<IteratorResult<T> | typeof IDLE_TRIP | typeof BUDGET_TRIP | typeof CALLER_ABORTED>\n\t\t\t> = [iterator.next(), idlePromise];\n\t\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\t\tif (budgetPromise) contenders.push(budgetPromise);\n\t\t\tlet result: IteratorResult<T> | typeof IDLE_TRIP | typeof BUDGET_TRIP | typeof CALLER_ABORTED;\n\t\t\ttry {\n\t\t\t\tresult = await Promise.race(contenders);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (result === IDLE_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\tthrow new StreamIdleTimeoutError(idleTimeoutMs);\n\t\t\t}\n\t\t\tif (result === BUDGET_TRIP) {\n\t\t\t\tabort();\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\tthrow new StreamDurationBudgetError(budgetMs);\n\t\t\t}\n\t\t\tif (result === CALLER_ABORTED) {\n\t\t\t\tvoid iterator?.return?.();\n\t\t\t\treturn await settleUnderWatchdogs();\n\t\t\t}\n\t\t\tif (result.done) return await settleUnderWatchdogs();\n\t\t\tonEvent?.(result.value);\n\t\t}\n\t} finally {\n\t\tremoveAbortListener?.();\n\t\tif (budgetTimer !== undefined) clearTimeout(budgetTimer);\n\t}\n}\n"]}
@@ -41,6 +41,12 @@ export type BindingEntryState = {
41
41
  export declare function storedBindingFromEntry(entry: BindingEntryState, hashes: readonly string[], anchor: StoredBindingAnchor): StoredBinding;
42
42
  /** Persist a completed turn whose resident registry entry closed before `message_end`. */
43
43
  export declare function storedBindingFromBinding(binding: ContinuityBinding, hashes: readonly string[], anchor: StoredBindingAnchor): StoredBinding | undefined;
44
+ /**
45
+ * Reason carried by the newest binding ledger record, when that record is an
46
+ * invalidation. A marker appended later retires it: the marker means a turn
47
+ * re-established the binding, so no cause is pending any more.
48
+ */
49
+ export declare function invalidationReasonFromBranch(branch: readonly BranchEntry[]): string | undefined;
44
50
  export declare function bindingFromStoredBranch(branch: readonly BranchEntry[], stored: StoredBinding): ContinuityBinding | undefined;
45
51
  export {};
46
52
  //# sourceMappingURL=session-binding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-binding.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,cAAc;aAAK,aAAa,EAAE,CAAC;aAAE,MAAM;CAAiB,CAAC;AAE1E,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IAClB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3D,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,MAAM,EAAE,mBAAmB,GACzB,aAAa,CAgBf;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,MAAM,EAAE,mBAAmB,GACzB,aAAa,GAAG,SAAS,CAsB3B;AAED,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,MAAM,EAAE,aAAa,GACnB,iBAAiB,GAAG,SAAS,CAW/B"}
1
+ {"version":3,"file":"session-binding.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,cAAc;aAAK,aAAa,EAAE,CAAC;aAAE,MAAM;CAAiB,CAAC;AAE1E,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IAClB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3D,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,MAAM,EAAE,mBAAmB,GACzB,aAAa,CAgBf;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,MAAM,EAAE,mBAAmB,GACzB,aAAa,GAAG,SAAS,CAsB3B;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,SAAS,CAK/F;AAOD,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,MAAM,EAAE,aAAa,GACnB,iBAAiB,GAAG,SAAS,CAW/B"}
@@ -53,6 +53,23 @@ export function storedBindingFromBinding(binding, hashes, anchor) {
53
53
  toolsetHash: binding.toolsetHash,
54
54
  };
55
55
  }
56
+ /**
57
+ * Reason carried by the newest binding ledger record, when that record is an
58
+ * invalidation. A marker appended later retires it: the marker means a turn
59
+ * re-established the binding, so no cause is pending any more.
60
+ */
61
+ export function invalidationReasonFromBranch(branch) {
62
+ const index = newestBindingEntryIndex(branch);
63
+ if (index < 0)
64
+ return undefined;
65
+ const data = branch[index]?.data;
66
+ return isBindingInvalidation(data) ? data.reason : undefined;
67
+ }
68
+ function isBindingInvalidation(value) {
69
+ if (typeof value !== "object" || value === null)
70
+ return false;
71
+ return "invalidated" in value && value.invalidated === true && "reason" in value && typeof value.reason === "string";
72
+ }
56
73
  export function bindingFromStoredBranch(branch, stored) {
57
74
  const markerIndex = newestBindingEntryIndex(branch);
58
75
  if (markerIndex < 0)
@@ -1 +1 @@
1
- {"version":3,"file":"session-binding.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { GOAL_CONTINUATION_MESSAGE_TYPE } from \"../../../messages.ts\";\nimport type { StoredBinding } from \"./session-binding-store.ts\";\nimport { assistantContentHash } from \"./session-commit-boundary.ts\";\nimport type { ContinuityBinding } from \"./session-reattach.ts\";\nimport { sentHashPrefixDigest } from \"./session-sync.ts\";\n\nexport const BINDING_ENTRY_TYPE = \"claude-sdk-oauth-binding\";\nexport const BINDING_MARKER = { schemaVersion: 2, marker: true } as const;\n\nexport type BindingInvalidation = {\n\treadonly schemaVersion: 1;\n\treadonly invalidated: true;\n\treadonly reason: string;\n};\n\ntype BranchEntry = {\n\treadonly id?: string;\n\treadonly type: string;\n\treadonly customType?: string;\n\treadonly data?: unknown;\n\treadonly message?: unknown;\n};\n\nexport type StoredBindingAnchor = {\n\treadonly sessionPath: string;\n\treadonly sessionId: string;\n\treadonly markerEntryId: string;\n\treadonly assistantContentHash: string;\n};\n\nexport type BindingEntryState = {\n\treadonly sdkSessionId: string;\n\treadonly accountName: string;\n\treadonly modelId: string;\n\treadonly systemPromptHash: string;\n\treadonly toolsetHash: string;\n\treadonly assistantUuidByIndex: ReadonlyMap<number, string>;\n};\n\n/**\n * The record is derived from the registry entry plus the hashes the branch\n * actually carries, never from the process binding map: that map holds the\n * previous turn's state while `message_end` runs (and only a prefix digest right\n * after a restart), so reading it would anchor this turn's marker to a stale or\n * absent sent-stream.\n */\nexport function storedBindingFromEntry(\n\tentry: BindingEntryState,\n\thashes: readonly string[],\n\tanchor: StoredBindingAnchor,\n): StoredBinding {\n\treturn {\n\t\tschemaVersion: 1,\n\t\tsessionPath: anchor.sessionPath,\n\t\tsessionId: anchor.sessionId,\n\t\tmarkerEntryId: anchor.markerEntryId,\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tsentCount: hashes.length,\n\t\tsentPrefixHash: sentHashPrefixDigest(hashes),\n\t\tassistantContentHash: anchor.assistantContentHash,\n\t\tlastAssistantUuid: entry.assistantUuidByIndex.get(hashes.length) ?? null,\n\t\taccountName: entry.accountName,\n\t\tmodelId: entry.modelId,\n\t\tsystemPromptHash: entry.systemPromptHash,\n\t\ttoolsetHash: entry.toolsetHash,\n\t};\n}\n\n/** Persist a completed turn whose resident registry entry closed before `message_end`. */\nexport function storedBindingFromBinding(\n\tbinding: ContinuityBinding,\n\thashes: readonly string[],\n\tanchor: StoredBindingAnchor,\n): StoredBinding | undefined {\n\tif (binding.sdkSessionIdConfirmed === false) return undefined;\n\tif (binding.sentCount !== hashes.length) return undefined;\n\tconst expectedDigest = sentHashPrefixDigest(hashes);\n\tconst bindingDigest =\n\t\tbinding.sentPrefixHash ?? (binding.sentHashes.length > 0 ? sentHashPrefixDigest(binding.sentHashes) : undefined);\n\tif (bindingDigest === undefined || bindingDigest !== expectedDigest) return undefined;\n\treturn {\n\t\tschemaVersion: 1,\n\t\tsessionPath: anchor.sessionPath,\n\t\tsessionId: anchor.sessionId,\n\t\tmarkerEntryId: anchor.markerEntryId,\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tsentCount: hashes.length,\n\t\tsentPrefixHash: sentHashPrefixDigest(hashes),\n\t\tassistantContentHash: anchor.assistantContentHash,\n\t\tlastAssistantUuid: binding.lastAssistantUuid,\n\t\taccountName: binding.accountName,\n\t\tmodelId: binding.modelId,\n\t\tsystemPromptHash: binding.systemPromptHash,\n\t\ttoolsetHash: binding.toolsetHash,\n\t};\n}\n\nexport function bindingFromStoredBranch(\n\tbranch: readonly BranchEntry[],\n\tstored: StoredBinding,\n): ContinuityBinding | undefined {\n\tconst markerIndex = newestBindingEntryIndex(branch);\n\tif (markerIndex < 0) return undefined;\n\tconst marker = branch[markerIndex];\n\tif (marker?.id !== stored.markerEntryId || !isBindingMarker(marker.data)) return undefined;\n\tconst assistantIndex = committedAssistantIndex(branch, markerIndex + 1);\n\tif (assistantIndex < 0 || !branch.slice(assistantIndex + 1).every(isLedgerOnlyEntry)) return undefined;\n\tconst committedAssistant = branch[assistantIndex]?.message;\n\tif (!isAssistantMessage(committedAssistant)) return undefined;\n\tif (assistantContentHash(committedAssistant) !== stored.assistantContentHash) return undefined;\n\treturn bindingFromStored(stored);\n}\n\n/**\n * The marker is appended inside `message_end`, so handlers that run after this\n * builtin (and extensions loaded later) can append ledger entries before the\n * assistant itself persists. The committed assistant is the first message after\n * the marker; anything that reaches the model in between fails closed.\n */\nfunction committedAssistantIndex(branch: readonly BranchEntry[], from: number): number {\n\tfor (let index = from; index < branch.length; index += 1) {\n\t\tconst entry = branch[index];\n\t\tif (entry?.type === \"message\") return index;\n\t\tif (!entry || !isLedgerOnlyEntry(entry)) return -1;\n\t}\n\treturn -1;\n}\n\n/**\n * Entry types the session-manager never projects into the LLM context. They\n * cannot shift the sent-stream digest the binding is verified against, so any\n * `custom` ledger record (hook state, rule scans, memory bookkeeping, ...) is\n * admitted regardless of who wrote it. Everything the model can see - messages,\n * custom messages other than the goal continuation, compaction and branch\n * summaries - keeps failing closed (oh-my-openagent#7925).\n */\nconst LEDGER_ONLY_ENTRY_TYPES: ReadonlySet<string> = new Set([\n\t\"custom\",\n\t\"label\",\n\t\"session_info\",\n\t\"thinking_level_change\",\n\t\"model_change\",\n\t// A refused switch (#1526) is bookkeeping the model never sees; omitting it\n\t// here made recording the refusal destroy the stored binding on resume.\n\t\"model_change_rejected\",\n\t\"configuration_update\",\n]);\n\nfunction isLedgerOnlyEntry(entry: BranchEntry): boolean {\n\tif (entry.type === \"custom_message\") return entry.customType === GOAL_CONTINUATION_MESSAGE_TYPE;\n\treturn LEDGER_ONLY_ENTRY_TYPES.has(entry.type);\n}\n\nfunction newestBindingEntryIndex(branch: readonly BranchEntry[]): number {\n\tfor (let index = branch.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = branch[index];\n\t\tif (entry?.type === \"custom\" && entry.customType === BINDING_ENTRY_TYPE) return index;\n\t}\n\treturn -1;\n}\n\nfunction isBindingMarker(value: unknown): boolean {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"schemaVersion\" in value && value.schemaVersion === 2 && \"marker\" in value && value.marker === true;\n}\n\nfunction isAssistantMessage(value: unknown): value is AssistantMessage {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn (\n\t\t\"role\" in value &&\n\t\tvalue.role === \"assistant\" &&\n\t\t\"api\" in value &&\n\t\ttypeof value.api === \"string\" &&\n\t\t\"provider\" in value &&\n\t\ttypeof value.provider === \"string\" &&\n\t\t\"model\" in value &&\n\t\ttypeof value.model === \"string\" &&\n\t\t\"content\" in value &&\n\t\tArray.isArray(value.content)\n\t);\n}\n\nfunction bindingFromStored(stored: StoredBinding): ContinuityBinding {\n\treturn {\n\t\tsenpiSessionId: stored.sessionId,\n\t\tsdkSessionId: stored.sdkSessionId,\n\t\tsentCount: stored.sentCount,\n\t\tsentHashes: [],\n\t\tsentPrefixHash: stored.sentPrefixHash,\n\t\tlastAssistantUuid: stored.lastAssistantUuid,\n\t\tassistantUuidByIndex: stored.lastAssistantUuid === null ? [] : [[stored.sentCount, stored.lastAssistantUuid]],\n\t\taccountName: stored.accountName,\n\t\tmodelId: stored.modelId,\n\t\tsystemPromptHash: stored.systemPromptHash,\n\t\ttoolsetHash: stored.toolsetHash,\n\t};\n}\n"]}
1
+ {"version":3,"file":"session-binding.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { GOAL_CONTINUATION_MESSAGE_TYPE } from \"../../../messages.ts\";\nimport type { StoredBinding } from \"./session-binding-store.ts\";\nimport { assistantContentHash } from \"./session-commit-boundary.ts\";\nimport type { ContinuityBinding } from \"./session-reattach.ts\";\nimport { sentHashPrefixDigest } from \"./session-sync.ts\";\n\nexport const BINDING_ENTRY_TYPE = \"claude-sdk-oauth-binding\";\nexport const BINDING_MARKER = { schemaVersion: 2, marker: true } as const;\n\nexport type BindingInvalidation = {\n\treadonly schemaVersion: 1;\n\treadonly invalidated: true;\n\treadonly reason: string;\n};\n\ntype BranchEntry = {\n\treadonly id?: string;\n\treadonly type: string;\n\treadonly customType?: string;\n\treadonly data?: unknown;\n\treadonly message?: unknown;\n};\n\nexport type StoredBindingAnchor = {\n\treadonly sessionPath: string;\n\treadonly sessionId: string;\n\treadonly markerEntryId: string;\n\treadonly assistantContentHash: string;\n};\n\nexport type BindingEntryState = {\n\treadonly sdkSessionId: string;\n\treadonly accountName: string;\n\treadonly modelId: string;\n\treadonly systemPromptHash: string;\n\treadonly toolsetHash: string;\n\treadonly assistantUuidByIndex: ReadonlyMap<number, string>;\n};\n\n/**\n * The record is derived from the registry entry plus the hashes the branch\n * actually carries, never from the process binding map: that map holds the\n * previous turn's state while `message_end` runs (and only a prefix digest right\n * after a restart), so reading it would anchor this turn's marker to a stale or\n * absent sent-stream.\n */\nexport function storedBindingFromEntry(\n\tentry: BindingEntryState,\n\thashes: readonly string[],\n\tanchor: StoredBindingAnchor,\n): StoredBinding {\n\treturn {\n\t\tschemaVersion: 1,\n\t\tsessionPath: anchor.sessionPath,\n\t\tsessionId: anchor.sessionId,\n\t\tmarkerEntryId: anchor.markerEntryId,\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tsentCount: hashes.length,\n\t\tsentPrefixHash: sentHashPrefixDigest(hashes),\n\t\tassistantContentHash: anchor.assistantContentHash,\n\t\tlastAssistantUuid: entry.assistantUuidByIndex.get(hashes.length) ?? null,\n\t\taccountName: entry.accountName,\n\t\tmodelId: entry.modelId,\n\t\tsystemPromptHash: entry.systemPromptHash,\n\t\ttoolsetHash: entry.toolsetHash,\n\t};\n}\n\n/** Persist a completed turn whose resident registry entry closed before `message_end`. */\nexport function storedBindingFromBinding(\n\tbinding: ContinuityBinding,\n\thashes: readonly string[],\n\tanchor: StoredBindingAnchor,\n): StoredBinding | undefined {\n\tif (binding.sdkSessionIdConfirmed === false) return undefined;\n\tif (binding.sentCount !== hashes.length) return undefined;\n\tconst expectedDigest = sentHashPrefixDigest(hashes);\n\tconst bindingDigest =\n\t\tbinding.sentPrefixHash ?? (binding.sentHashes.length > 0 ? sentHashPrefixDigest(binding.sentHashes) : undefined);\n\tif (bindingDigest === undefined || bindingDigest !== expectedDigest) return undefined;\n\treturn {\n\t\tschemaVersion: 1,\n\t\tsessionPath: anchor.sessionPath,\n\t\tsessionId: anchor.sessionId,\n\t\tmarkerEntryId: anchor.markerEntryId,\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tsentCount: hashes.length,\n\t\tsentPrefixHash: sentHashPrefixDigest(hashes),\n\t\tassistantContentHash: anchor.assistantContentHash,\n\t\tlastAssistantUuid: binding.lastAssistantUuid,\n\t\taccountName: binding.accountName,\n\t\tmodelId: binding.modelId,\n\t\tsystemPromptHash: binding.systemPromptHash,\n\t\ttoolsetHash: binding.toolsetHash,\n\t};\n}\n\n/**\n * Reason carried by the newest binding ledger record, when that record is an\n * invalidation. A marker appended later retires it: the marker means a turn\n * re-established the binding, so no cause is pending any more.\n */\nexport function invalidationReasonFromBranch(branch: readonly BranchEntry[]): string | undefined {\n\tconst index = newestBindingEntryIndex(branch);\n\tif (index < 0) return undefined;\n\tconst data = branch[index]?.data;\n\treturn isBindingInvalidation(data) ? data.reason : undefined;\n}\n\nfunction isBindingInvalidation(value: unknown): value is BindingInvalidation {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"invalidated\" in value && value.invalidated === true && \"reason\" in value && typeof value.reason === \"string\";\n}\n\nexport function bindingFromStoredBranch(\n\tbranch: readonly BranchEntry[],\n\tstored: StoredBinding,\n): ContinuityBinding | undefined {\n\tconst markerIndex = newestBindingEntryIndex(branch);\n\tif (markerIndex < 0) return undefined;\n\tconst marker = branch[markerIndex];\n\tif (marker?.id !== stored.markerEntryId || !isBindingMarker(marker.data)) return undefined;\n\tconst assistantIndex = committedAssistantIndex(branch, markerIndex + 1);\n\tif (assistantIndex < 0 || !branch.slice(assistantIndex + 1).every(isLedgerOnlyEntry)) return undefined;\n\tconst committedAssistant = branch[assistantIndex]?.message;\n\tif (!isAssistantMessage(committedAssistant)) return undefined;\n\tif (assistantContentHash(committedAssistant) !== stored.assistantContentHash) return undefined;\n\treturn bindingFromStored(stored);\n}\n\n/**\n * The marker is appended inside `message_end`, so handlers that run after this\n * builtin (and extensions loaded later) can append ledger entries before the\n * assistant itself persists. The committed assistant is the first message after\n * the marker; anything that reaches the model in between fails closed.\n */\nfunction committedAssistantIndex(branch: readonly BranchEntry[], from: number): number {\n\tfor (let index = from; index < branch.length; index += 1) {\n\t\tconst entry = branch[index];\n\t\tif (entry?.type === \"message\") return index;\n\t\tif (!entry || !isLedgerOnlyEntry(entry)) return -1;\n\t}\n\treturn -1;\n}\n\n/**\n * Entry types the session-manager never projects into the LLM context. They\n * cannot shift the sent-stream digest the binding is verified against, so any\n * `custom` ledger record (hook state, rule scans, memory bookkeeping, ...) is\n * admitted regardless of who wrote it. Everything the model can see - messages,\n * custom messages other than the goal continuation, compaction and branch\n * summaries - keeps failing closed (oh-my-openagent#7925).\n */\nconst LEDGER_ONLY_ENTRY_TYPES: ReadonlySet<string> = new Set([\n\t\"custom\",\n\t\"label\",\n\t\"session_info\",\n\t\"thinking_level_change\",\n\t\"model_change\",\n\t// A refused switch (#1526) is bookkeeping the model never sees; omitting it\n\t// here made recording the refusal destroy the stored binding on resume.\n\t\"model_change_rejected\",\n\t\"configuration_update\",\n]);\n\nfunction isLedgerOnlyEntry(entry: BranchEntry): boolean {\n\tif (entry.type === \"custom_message\") return entry.customType === GOAL_CONTINUATION_MESSAGE_TYPE;\n\treturn LEDGER_ONLY_ENTRY_TYPES.has(entry.type);\n}\n\nfunction newestBindingEntryIndex(branch: readonly BranchEntry[]): number {\n\tfor (let index = branch.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = branch[index];\n\t\tif (entry?.type === \"custom\" && entry.customType === BINDING_ENTRY_TYPE) return index;\n\t}\n\treturn -1;\n}\n\nfunction isBindingMarker(value: unknown): boolean {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"schemaVersion\" in value && value.schemaVersion === 2 && \"marker\" in value && value.marker === true;\n}\n\nfunction isAssistantMessage(value: unknown): value is AssistantMessage {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn (\n\t\t\"role\" in value &&\n\t\tvalue.role === \"assistant\" &&\n\t\t\"api\" in value &&\n\t\ttypeof value.api === \"string\" &&\n\t\t\"provider\" in value &&\n\t\ttypeof value.provider === \"string\" &&\n\t\t\"model\" in value &&\n\t\ttypeof value.model === \"string\" &&\n\t\t\"content\" in value &&\n\t\tArray.isArray(value.content)\n\t);\n}\n\nfunction bindingFromStored(stored: StoredBinding): ContinuityBinding {\n\treturn {\n\t\tsenpiSessionId: stored.sessionId,\n\t\tsdkSessionId: stored.sdkSessionId,\n\t\tsentCount: stored.sentCount,\n\t\tsentHashes: [],\n\t\tsentPrefixHash: stored.sentPrefixHash,\n\t\tlastAssistantUuid: stored.lastAssistantUuid,\n\t\tassistantUuidByIndex: stored.lastAssistantUuid === null ? [] : [[stored.sentCount, stored.lastAssistantUuid]],\n\t\taccountName: stored.accountName,\n\t\tmodelId: stored.modelId,\n\t\tsystemPromptHash: stored.systemPromptHash,\n\t\ttoolsetHash: stored.toolsetHash,\n\t};\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ContinuityReason } from "./session-observability.ts";
1
+ import { type ContinuityReason } from "./session-observability.ts";
2
2
  export type ContinuityEntrySnapshot = {
3
3
  sdkSessionId: string;
4
4
  accountName: string;
@@ -41,9 +41,12 @@ export type ContinuityDecisionInput = {
41
41
  /** false only on the config-dir lane, whose per-account credential roots cannot share a transcript root, so cross-account resume is impossible there. */
42
42
  crossAccountResumeSupported: boolean;
43
43
  idleExpired?: boolean;
44
+ /** Reason the newest ledger record invalidated this session's binding, when one is pending. */
45
+ invalidationReason?: string;
44
46
  };
45
47
  export type ContinuityDecision = {
46
48
  kind: "bootstrap";
49
+ reason?: ContinuityReason;
47
50
  } | {
48
51
  kind: "delta";
49
52
  from: number;
@@ -1 +1 @@
1
- {"version":3,"file":"session-continuity.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnE,MAAM,MAAM,uBAAuB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sGAAsG;IACtG,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAC/C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,mBAAmB,EAAE,OAAO,CAAC;IAC7B,yJAAyJ;IACzJ,2BAA2B,EAAE,OAAO,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAClF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC9F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AA6JjD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CA8BzF"}
1
+ {"version":3,"file":"session-continuity.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAkB,MAAM,4BAA4B,CAAC;AAGnF,MAAM,MAAM,uBAAuB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sGAAsG;IACtG,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAC/C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,mBAAmB,EAAE,OAAO,CAAC;IAC7B,yJAAyJ;IACzJ,2BAA2B,EAAE,OAAO,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAClF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC9F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAuLjD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CAEzF"}
@@ -1,8 +1,36 @@
1
+ import { sanitizeReason } from "./session-observability.js";
1
2
  import { sentHashPrefixDigest } from "./session-sync.js";
2
3
  const PENDING_FORK_REASONS = {
3
4
  assistant_rewritten: "assistant_rewritten",
4
5
  compaction: "tainted_compaction",
5
6
  };
7
+ /**
8
+ * Ledger invalidation reasons that are not themselves observation vocabulary,
9
+ * mapped onto the closest member. Reasons that ARE members (model_selected,
10
+ * extensions_removed, assistant_rewritten) pass through `sanitizeReason`, which
11
+ * also keeps an unknown ledger string from ever reaching an observation.
12
+ */
13
+ const INVALIDATION_CAUSES = {
14
+ compaction: "tainted_compaction",
15
+ tree_changed: "branch_diverged",
16
+ fork: "tainted_fork",
17
+ };
18
+ /**
19
+ * `registry_miss` means what it says: no record was ever found. When the ledger
20
+ * recorded WHY the binding went away, the cold-seed names that cause instead.
21
+ * Classification is untouched - only the reason a `bootstrap`/`flatten` reports
22
+ * changes, and only when it would otherwise be the no-record default.
23
+ */
24
+ function withRecordedInvalidation(decision, input) {
25
+ if (input.invalidationReason === undefined)
26
+ return decision;
27
+ const cause = INVALIDATION_CAUSES[input.invalidationReason] ?? sanitizeReason(input.invalidationReason);
28
+ if (decision.kind === "bootstrap")
29
+ return { kind: "bootstrap", reason: cause };
30
+ if (decision.kind === "flatten" && decision.reason === "registry_miss")
31
+ return { kind: "flatten", reason: cause };
32
+ return decision;
33
+ }
6
34
  function commonPrefixLength(left, right) {
7
35
  const limit = Math.min(left.length, right.length);
8
36
  let index = 0;
@@ -155,6 +183,9 @@ function decideFromBinding(input, binding) {
155
183
  * session, new query).
156
184
  */
157
185
  export function decideNativeContinuity(input) {
186
+ return withRecordedInvalidation(decideFromState(input), input);
187
+ }
188
+ function decideFromState(input) {
158
189
  const { entry, binding } = input;
159
190
  if (!entry) {
160
191
  if (!binding)
@@ -1 +1 @@
1
- {"version":3,"file":"session-continuity.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContinuityReason } from \"./session-observability.ts\";\nimport { sentHashPrefixDigest } from \"./session-sync.ts\";\n\nexport type ContinuityEntrySnapshot = {\n\tsdkSessionId: string;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tlastAssistantUuid: string | null;\n\tassistantUuidByIndex: ReadonlyMap<number, string>;\n\tpendingForkReason: string | null;\n\ttaintedReason?: string | null;\n};\n\nexport type ContinuityBindingSnapshot = {\n\tsdkSessionId: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tsentPrefixHash?: string;\n\tlastAssistantUuid: string | null;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\t/** Sent-stream digest of a turn that was pushed but never answered (retry checkpoint). */\n\tunansweredTurnDigest?: string;\n\t/** False until the SDK acknowledged the id; a resume/fork of an unconfirmed id is never attempted. */\n\tsdkSessionIdConfirmed?: boolean;\n};\n\nexport type ContinuityDecisionInput = {\n\tentry: ContinuityEntrySnapshot | undefined;\n\tbinding: ContinuityBindingSnapshot | undefined;\n\tcurrentHashes: readonly string[];\n\taccountName: string;\n\tmodelId: string;\n\tfingerprint: { systemPromptHash: string; toolsetHash: string };\n\ttranscriptAvailable: boolean;\n\t/** false only on the config-dir lane, whose per-account credential roots cannot share a transcript root, so cross-account resume is impossible there. */\n\tcrossAccountResumeSupported: boolean;\n\tidleExpired?: boolean;\n};\n\nexport type ContinuityDecision =\n\t| { kind: \"bootstrap\" }\n\t| { kind: \"delta\"; from: number }\n\t| { kind: \"reattach\"; sdkSessionId: string; from: number; reason: ContinuityReason }\n\t| { kind: \"fork\"; sdkSessionId: string; atUuid: string; from: number; reason: ContinuityReason }\n\t| { kind: \"flatten\"; reason: ContinuityReason };\n\nconst PENDING_FORK_REASONS: Readonly<Record<string, ContinuityReason>> = {\n\tassistant_rewritten: \"assistant_rewritten\",\n\tcompaction: \"tainted_compaction\",\n};\n\nfunction commonPrefixLength(left: readonly string[], right: readonly string[]): number {\n\tconst limit = Math.min(left.length, right.length);\n\tlet index = 0;\n\twhile (index < limit && left[index] === right[index]) index += 1;\n\treturn index;\n}\n\n/**\n * The fork point is the last assistant boundary STRICTLY BEFORE the divergence:\n * forking at the diverged turn itself would carry the stale assistant into the new\n * branch and leave nothing to re-send.\n */\nfunction boundaryBefore(entry: ContinuityEntrySnapshot, count: number): { index: number; uuid: string } | undefined {\n\tfor (let candidate = count - 1; candidate >= 1; candidate -= 1) {\n\t\tconst uuid = entry.assistantUuidByIndex.get(candidate);\n\t\tif (uuid) return { index: candidate, uuid };\n\t}\n\treturn undefined;\n}\n\nfunction forkOrFlatten(\n\tentry: ContinuityEntrySnapshot,\n\tdivergesAt: number,\n\treason: ContinuityReason,\n): ContinuityDecision {\n\tconst boundary = boundaryBefore(entry, divergesAt);\n\tif (!boundary) return { kind: \"flatten\", reason };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tatUuid: boundary.uuid,\n\t\tfrom: boundary.index,\n\t\treason,\n\t};\n}\n\nfunction identityDrift(\n\tinput: ContinuityDecisionInput,\n\tentry: Pick<ContinuityEntrySnapshot, \"accountName\" | \"modelId\" | \"systemPromptHash\" | \"toolsetHash\">,\n): ContinuityReason | null {\n\tif (entry.accountName !== input.accountName) return \"account_changed\";\n\tif (entry.modelId !== input.modelId) return \"model_changed\";\n\tif (entry.systemPromptHash !== input.fingerprint.systemPromptHash) return \"system_prompt_changed\";\n\tif (entry.toolsetHash !== input.fingerprint.toolsetHash) return \"toolset_changed\";\n\treturn null;\n}\n\n/**\n * Same-turn retry after a stream-start timeout: the abandoned attempt already\n * appended its user message to the lineage, so re-attaching would append it a\n * SECOND time and re-bill the whole conversation. Forking at the pre-turn\n * assistant boundary rewinds past the un-answered message, so the retry's\n * request byte-layout matches the failed attempt's (prefix cache read).\n * Requires the FULL current turn to hash-match the checkpoint, so a different\n * turn falls through to the ordinary branches below.\n */\nfunction retryCheckpointDecision(\n\tinput: ContinuityDecisionInput,\n\tbinding: ContinuityBindingSnapshot,\n): ContinuityDecision | undefined {\n\tif (binding.unansweredTurnDigest === undefined) return undefined;\n\tif (sentHashPrefixDigest(input.currentHashes, input.currentHashes.length) !== binding.unansweredTurnDigest) {\n\t\treturn undefined;\n\t}\n\tif (input.currentHashes.length < binding.sentCount) return undefined;\n\tconst prefixMatches =\n\t\tbinding.sentPrefixHash !== undefined\n\t\t\t? sentHashPrefixDigest(input.currentHashes, binding.sentCount) === binding.sentPrefixHash\n\t\t\t: commonPrefixLength(binding.sentHashes, input.currentHashes) === binding.sentCount;\n\tif (!prefixMatches) return undefined;\n\tif (!binding.lastAssistantUuid) return { kind: \"flatten\", reason: \"timeout_retry\" };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tatUuid: binding.lastAssistantUuid,\n\t\tfrom: binding.sentCount,\n\t\treason: \"timeout_retry\",\n\t};\n}\n\n/**\n * A binding whose SDK id was minted locally and never acknowledged (no init, no\n * replay echo before the attempt failed) must not be resumed: Claude Code\n * answers \"No conversation found with session ID\" and every retry would mint\n * another dead id (oh-my-openagent#7562). Cold-seed instead.\n */\nfunction withoutUnconfirmedResume(\n\tdecision: ContinuityDecision,\n\tbinding: ContinuityBindingSnapshot,\n): ContinuityDecision {\n\tif (binding.sdkSessionIdConfirmed !== false) return decision;\n\tif (decision.kind === \"reattach\" || decision.kind === \"fork\") {\n\t\treturn { kind: \"flatten\", reason: \"session_unconfirmed\" };\n\t}\n\treturn decision;\n}\n\nfunction decideFromBinding(input: ContinuityDecisionInput, binding: ContinuityBindingSnapshot): ContinuityDecision {\n\tif (!input.transcriptAvailable) return { kind: \"flatten\", reason: \"transcript_missing\" };\n\tconst drift = identityDrift(input, binding);\n\t// Model identity drift fails closed: the persisted identity no longer matches the turn.\n\t// Account drift flattens only on the config-dir lane, whose per-account roots cannot\n\t// share a transcript; on shared-root lanes it falls through like prompt/toolset drift\n\t// (senpi#1432), so the retry checkpoint forks a same-turn failover at the pre-turn\n\t// boundary and a matching prefix reattaches with reason account_changed.\n\tif (drift === \"model_changed\") return { kind: \"flatten\", reason: drift };\n\tif (drift === \"account_changed\" && !input.crossAccountResumeSupported)\n\t\treturn { kind: \"flatten\", reason: \"cross_root_unsupported\" };\n\t// Prompt/toolset drift instead reattaches like the live path\n\t// (oh-my-openagent#7884) - a restart has no live query, so the resume builds a\n\t// fresh query carrying the CURRENT options and hooks, and flattening would\n\t// re-send the whole conversation for drift the SDK applies per-query anyway.\n\tconst retry = retryCheckpointDecision(input, binding);\n\tif (retry) return retry;\n\tif (binding.sentPrefixHash !== undefined) {\n\t\tconst prefixMatches =\n\t\t\tinput.currentHashes.length >= binding.sentCount &&\n\t\t\tsentHashPrefixDigest(input.currentHashes, binding.sentCount) === binding.sentPrefixHash;\n\t\tif (prefixMatches) {\n\t\t\treturn {\n\t\t\t\tkind: \"reattach\",\n\t\t\t\tsdkSessionId: binding.sdkSessionId,\n\t\t\t\tfrom: binding.sentCount,\n\t\t\t\treason: drift ?? \"registry_miss\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tkind: \"flatten\",\n\t\t\treason: input.currentHashes.length < binding.sentCount ? \"history_rolled_back\" : \"sent_stream_diverged\",\n\t\t};\n\t}\n\tconst shared = commonPrefixLength(binding.sentHashes, input.currentHashes);\n\tif (shared === binding.sentCount) {\n\t\treturn {\n\t\t\tkind: \"reattach\",\n\t\t\tsdkSessionId: binding.sdkSessionId,\n\t\t\tfrom: binding.sentCount,\n\t\t\treason: drift ?? \"registry_miss\",\n\t\t};\n\t}\n\tif (!binding.lastAssistantUuid) return { kind: \"flatten\", reason: \"registry_miss\" };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tatUuid: binding.lastAssistantUuid,\n\t\tfrom: shared,\n\t\treason: shared < binding.sentCount ? \"history_rolled_back\" : \"sent_stream_diverged\",\n\t};\n}\n\n/**\n * Resume-first: a live session is never abandoned for a flattened re-send. Only a\n * missing transcript, an unrecoverable boundary, a model identity drift, or account\n * drift on the config-dir lane on a persisted binding reaches `flatten`; every other\n * divergence resolves to `fork` (same lineage, new branch) or `reattach` (same\n * session, new query).\n */\nexport function decideNativeContinuity(input: ContinuityDecisionInput): ContinuityDecision {\n\tconst { entry, binding } = input;\n\tif (!entry) {\n\t\tif (!binding) return { kind: \"bootstrap\" };\n\t\treturn withoutUnconfirmedResume(decideFromBinding(input, binding), binding);\n\t}\n\n\tconst divergence = entry.pendingForkReason ?? entry.taintedReason;\n\tif (divergence) {\n\t\treturn forkOrFlatten(entry, entry.sentCount, PENDING_FORK_REASONS[divergence] ?? \"other\");\n\t}\n\n\tconst shared = commonPrefixLength(entry.sentHashes, input.currentHashes);\n\tif (shared < entry.sentCount) {\n\t\tconst rolledBack = input.currentHashes.length < entry.sentCount && shared === input.currentHashes.length;\n\t\treturn rolledBack\n\t\t\t? forkOrFlatten(entry, input.currentHashes.length, \"history_rolled_back\")\n\t\t\t: forkOrFlatten(entry, shared + 1, \"sent_stream_diverged\");\n\t}\n\n\tif (input.idleExpired) {\n\t\treturn { kind: \"reattach\", sdkSessionId: entry.sdkSessionId, from: entry.sentCount, reason: \"idle_ttl\" };\n\t}\n\n\tconst drift = identityDrift(input, entry);\n\tif (drift) {\n\t\treturn { kind: \"reattach\", sdkSessionId: entry.sdkSessionId, from: entry.sentCount, reason: drift };\n\t}\n\n\treturn { kind: \"delta\", from: entry.sentCount };\n}\n"]}
1
+ {"version":3,"file":"session-continuity.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAsDzD,MAAM,oBAAoB,GAA+C;IACxE,mBAAmB,EAAE,qBAAqB;IAC1C,UAAU,EAAE,oBAAoB;CAChC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,mBAAmB,GAA+C;IACvE,UAAU,EAAE,oBAAoB;IAChC,YAAY,EAAE,iBAAiB;IAC/B,IAAI,EAAE,cAAc;CACpB,CAAC;AAEF;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,QAA4B,EAAE,KAA8B;IAC7F,IAAI,KAAK,CAAC,kBAAkB,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5D,MAAM,KAAK,GAAG,mBAAmB,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,cAAc,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACxG,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC/E,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,eAAe;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAClH,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAuB,EAAE,KAAwB;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC;QAAE,KAAK,IAAI,CAAC,CAAC;IACjE,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,KAA8B,EAAE,KAAa;IACpE,KAAK,IAAI,SAAS,GAAG,KAAK,GAAG,CAAC,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,IAAI,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,IAAI;YAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CACrB,KAA8B,EAC9B,UAAkB,EAClB,MAAwB;IAExB,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACnD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAClD,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,QAAQ,CAAC,IAAI;QACrB,IAAI,EAAE,QAAQ,CAAC,KAAK;QACpB,MAAM;KACN,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CACrB,KAA8B,EAC9B,KAAoG;IAEpG,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW;QAAE,OAAO,iBAAiB,CAAC;IACtE,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;QAAE,OAAO,eAAe,CAAC;IAC5D,IAAI,KAAK,CAAC,gBAAgB,KAAK,KAAK,CAAC,WAAW,CAAC,gBAAgB;QAAE,OAAO,uBAAuB,CAAC;IAClG,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,CAAC,WAAW;QAAE,OAAO,iBAAiB,CAAC;IAClF,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,uBAAuB,CAC/B,KAA8B,EAC9B,OAAkC;IAElC,IAAI,OAAO,CAAC,oBAAoB,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACjE,IAAI,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,oBAAoB,EAAE,CAAC;QAC5G,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACrE,MAAM,aAAa,GAClB,OAAO,CAAC,cAAc,KAAK,SAAS;QACnC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,cAAc;QACzF,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC,SAAS,CAAC;IACtF,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACrC,IAAI,CAAC,OAAO,CAAC,iBAAiB;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACpF,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,OAAO,CAAC,iBAAiB;QACjC,IAAI,EAAE,OAAO,CAAC,SAAS;QACvB,MAAM,EAAE,eAAe;KACvB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,wBAAwB,CAChC,QAA4B,EAC5B,OAAkC;IAElC,IAAI,OAAO,CAAC,qBAAqB,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC7D,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAC3D,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA8B,EAAE,OAAkC;IAC5F,IAAI,CAAC,KAAK,CAAC,mBAAmB;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACzF,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5C,wFAAwF;IACxF,qFAAqF;IACrF,sFAAsF;IACtF,mFAAmF;IACnF,yEAAyE;IACzE,IAAI,KAAK,KAAK,eAAe;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACzE,IAAI,KAAK,KAAK,iBAAiB,IAAI,CAAC,KAAK,CAAC,2BAA2B;QACpE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IAC9D,6DAA6D;IAC7D,+EAA+E;IAC/E,2EAA2E;IAC3E,6EAA6E;IAC7E,MAAM,KAAK,GAAG,uBAAuB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,aAAa,GAClB,KAAK,CAAC,aAAa,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;YAC/C,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,cAAc,CAAC;QACzF,IAAI,aAAa,EAAE,CAAC;YACnB,OAAO;gBACN,IAAI,EAAE,UAAU;gBAChB,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,IAAI,EAAE,OAAO,CAAC,SAAS;gBACvB,MAAM,EAAE,KAAK,IAAI,eAAe;aAChC,CAAC;QACH,CAAC;QACD,OAAO;YACN,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,sBAAsB;SACvG,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC3E,IAAI,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;QAClC,OAAO;YACN,IAAI,EAAE,UAAU;YAChB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,IAAI,EAAE,OAAO,CAAC,SAAS;YACvB,MAAM,EAAE,KAAK,IAAI,eAAe;SAChC,CAAC;IACH,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,iBAAiB;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACpF,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,OAAO,CAAC,iBAAiB;QACjC,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,sBAAsB;KACnF,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAA8B;IACpE,OAAO,wBAAwB,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,eAAe,CAAC,KAA8B;IACtD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC3C,OAAO,wBAAwB,CAAC,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,aAAa,CAAC;IAClE,IAAI,UAAU,EAAE,CAAC;QAChB,OAAO,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACzE,IAAI,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,KAAK,CAAC,SAAS,IAAI,MAAM,KAAK,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC;QACzG,OAAO,UAAU;YAChB,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,qBAAqB,CAAC;YACzE,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC1G,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC1C,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACrG,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;AACjD,CAAC","sourcesContent":["import { type ContinuityReason, sanitizeReason } from \"./session-observability.ts\";\nimport { sentHashPrefixDigest } from \"./session-sync.ts\";\n\nexport type ContinuityEntrySnapshot = {\n\tsdkSessionId: string;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tlastAssistantUuid: string | null;\n\tassistantUuidByIndex: ReadonlyMap<number, string>;\n\tpendingForkReason: string | null;\n\ttaintedReason?: string | null;\n};\n\nexport type ContinuityBindingSnapshot = {\n\tsdkSessionId: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tsentPrefixHash?: string;\n\tlastAssistantUuid: string | null;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\t/** Sent-stream digest of a turn that was pushed but never answered (retry checkpoint). */\n\tunansweredTurnDigest?: string;\n\t/** False until the SDK acknowledged the id; a resume/fork of an unconfirmed id is never attempted. */\n\tsdkSessionIdConfirmed?: boolean;\n};\n\nexport type ContinuityDecisionInput = {\n\tentry: ContinuityEntrySnapshot | undefined;\n\tbinding: ContinuityBindingSnapshot | undefined;\n\tcurrentHashes: readonly string[];\n\taccountName: string;\n\tmodelId: string;\n\tfingerprint: { systemPromptHash: string; toolsetHash: string };\n\ttranscriptAvailable: boolean;\n\t/** false only on the config-dir lane, whose per-account credential roots cannot share a transcript root, so cross-account resume is impossible there. */\n\tcrossAccountResumeSupported: boolean;\n\tidleExpired?: boolean;\n\t/** Reason the newest ledger record invalidated this session's binding, when one is pending. */\n\tinvalidationReason?: string;\n};\n\nexport type ContinuityDecision =\n\t| { kind: \"bootstrap\"; reason?: ContinuityReason }\n\t| { kind: \"delta\"; from: number }\n\t| { kind: \"reattach\"; sdkSessionId: string; from: number; reason: ContinuityReason }\n\t| { kind: \"fork\"; sdkSessionId: string; atUuid: string; from: number; reason: ContinuityReason }\n\t| { kind: \"flatten\"; reason: ContinuityReason };\n\nconst PENDING_FORK_REASONS: Readonly<Record<string, ContinuityReason>> = {\n\tassistant_rewritten: \"assistant_rewritten\",\n\tcompaction: \"tainted_compaction\",\n};\n\n/**\n * Ledger invalidation reasons that are not themselves observation vocabulary,\n * mapped onto the closest member. Reasons that ARE members (model_selected,\n * extensions_removed, assistant_rewritten) pass through `sanitizeReason`, which\n * also keeps an unknown ledger string from ever reaching an observation.\n */\nconst INVALIDATION_CAUSES: Readonly<Record<string, ContinuityReason>> = {\n\tcompaction: \"tainted_compaction\",\n\ttree_changed: \"branch_diverged\",\n\tfork: \"tainted_fork\",\n};\n\n/**\n * `registry_miss` means what it says: no record was ever found. When the ledger\n * recorded WHY the binding went away, the cold-seed names that cause instead.\n * Classification is untouched - only the reason a `bootstrap`/`flatten` reports\n * changes, and only when it would otherwise be the no-record default.\n */\nfunction withRecordedInvalidation(decision: ContinuityDecision, input: ContinuityDecisionInput): ContinuityDecision {\n\tif (input.invalidationReason === undefined) return decision;\n\tconst cause = INVALIDATION_CAUSES[input.invalidationReason] ?? sanitizeReason(input.invalidationReason);\n\tif (decision.kind === \"bootstrap\") return { kind: \"bootstrap\", reason: cause };\n\tif (decision.kind === \"flatten\" && decision.reason === \"registry_miss\") return { kind: \"flatten\", reason: cause };\n\treturn decision;\n}\n\nfunction commonPrefixLength(left: readonly string[], right: readonly string[]): number {\n\tconst limit = Math.min(left.length, right.length);\n\tlet index = 0;\n\twhile (index < limit && left[index] === right[index]) index += 1;\n\treturn index;\n}\n\n/**\n * The fork point is the last assistant boundary STRICTLY BEFORE the divergence:\n * forking at the diverged turn itself would carry the stale assistant into the new\n * branch and leave nothing to re-send.\n */\nfunction boundaryBefore(entry: ContinuityEntrySnapshot, count: number): { index: number; uuid: string } | undefined {\n\tfor (let candidate = count - 1; candidate >= 1; candidate -= 1) {\n\t\tconst uuid = entry.assistantUuidByIndex.get(candidate);\n\t\tif (uuid) return { index: candidate, uuid };\n\t}\n\treturn undefined;\n}\n\nfunction forkOrFlatten(\n\tentry: ContinuityEntrySnapshot,\n\tdivergesAt: number,\n\treason: ContinuityReason,\n): ContinuityDecision {\n\tconst boundary = boundaryBefore(entry, divergesAt);\n\tif (!boundary) return { kind: \"flatten\", reason };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tatUuid: boundary.uuid,\n\t\tfrom: boundary.index,\n\t\treason,\n\t};\n}\n\nfunction identityDrift(\n\tinput: ContinuityDecisionInput,\n\tentry: Pick<ContinuityEntrySnapshot, \"accountName\" | \"modelId\" | \"systemPromptHash\" | \"toolsetHash\">,\n): ContinuityReason | null {\n\tif (entry.accountName !== input.accountName) return \"account_changed\";\n\tif (entry.modelId !== input.modelId) return \"model_changed\";\n\tif (entry.systemPromptHash !== input.fingerprint.systemPromptHash) return \"system_prompt_changed\";\n\tif (entry.toolsetHash !== input.fingerprint.toolsetHash) return \"toolset_changed\";\n\treturn null;\n}\n\n/**\n * Same-turn retry after a stream-start timeout: the abandoned attempt already\n * appended its user message to the lineage, so re-attaching would append it a\n * SECOND time and re-bill the whole conversation. Forking at the pre-turn\n * assistant boundary rewinds past the un-answered message, so the retry's\n * request byte-layout matches the failed attempt's (prefix cache read).\n * Requires the FULL current turn to hash-match the checkpoint, so a different\n * turn falls through to the ordinary branches below.\n */\nfunction retryCheckpointDecision(\n\tinput: ContinuityDecisionInput,\n\tbinding: ContinuityBindingSnapshot,\n): ContinuityDecision | undefined {\n\tif (binding.unansweredTurnDigest === undefined) return undefined;\n\tif (sentHashPrefixDigest(input.currentHashes, input.currentHashes.length) !== binding.unansweredTurnDigest) {\n\t\treturn undefined;\n\t}\n\tif (input.currentHashes.length < binding.sentCount) return undefined;\n\tconst prefixMatches =\n\t\tbinding.sentPrefixHash !== undefined\n\t\t\t? sentHashPrefixDigest(input.currentHashes, binding.sentCount) === binding.sentPrefixHash\n\t\t\t: commonPrefixLength(binding.sentHashes, input.currentHashes) === binding.sentCount;\n\tif (!prefixMatches) return undefined;\n\tif (!binding.lastAssistantUuid) return { kind: \"flatten\", reason: \"timeout_retry\" };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tatUuid: binding.lastAssistantUuid,\n\t\tfrom: binding.sentCount,\n\t\treason: \"timeout_retry\",\n\t};\n}\n\n/**\n * A binding whose SDK id was minted locally and never acknowledged (no init, no\n * replay echo before the attempt failed) must not be resumed: Claude Code\n * answers \"No conversation found with session ID\" and every retry would mint\n * another dead id (oh-my-openagent#7562). Cold-seed instead.\n */\nfunction withoutUnconfirmedResume(\n\tdecision: ContinuityDecision,\n\tbinding: ContinuityBindingSnapshot,\n): ContinuityDecision {\n\tif (binding.sdkSessionIdConfirmed !== false) return decision;\n\tif (decision.kind === \"reattach\" || decision.kind === \"fork\") {\n\t\treturn { kind: \"flatten\", reason: \"session_unconfirmed\" };\n\t}\n\treturn decision;\n}\n\nfunction decideFromBinding(input: ContinuityDecisionInput, binding: ContinuityBindingSnapshot): ContinuityDecision {\n\tif (!input.transcriptAvailable) return { kind: \"flatten\", reason: \"transcript_missing\" };\n\tconst drift = identityDrift(input, binding);\n\t// Model identity drift fails closed: the persisted identity no longer matches the turn.\n\t// Account drift flattens only on the config-dir lane, whose per-account roots cannot\n\t// share a transcript; on shared-root lanes it falls through like prompt/toolset drift\n\t// (senpi#1432), so the retry checkpoint forks a same-turn failover at the pre-turn\n\t// boundary and a matching prefix reattaches with reason account_changed.\n\tif (drift === \"model_changed\") return { kind: \"flatten\", reason: drift };\n\tif (drift === \"account_changed\" && !input.crossAccountResumeSupported)\n\t\treturn { kind: \"flatten\", reason: \"cross_root_unsupported\" };\n\t// Prompt/toolset drift instead reattaches like the live path\n\t// (oh-my-openagent#7884) - a restart has no live query, so the resume builds a\n\t// fresh query carrying the CURRENT options and hooks, and flattening would\n\t// re-send the whole conversation for drift the SDK applies per-query anyway.\n\tconst retry = retryCheckpointDecision(input, binding);\n\tif (retry) return retry;\n\tif (binding.sentPrefixHash !== undefined) {\n\t\tconst prefixMatches =\n\t\t\tinput.currentHashes.length >= binding.sentCount &&\n\t\t\tsentHashPrefixDigest(input.currentHashes, binding.sentCount) === binding.sentPrefixHash;\n\t\tif (prefixMatches) {\n\t\t\treturn {\n\t\t\t\tkind: \"reattach\",\n\t\t\t\tsdkSessionId: binding.sdkSessionId,\n\t\t\t\tfrom: binding.sentCount,\n\t\t\t\treason: drift ?? \"registry_miss\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tkind: \"flatten\",\n\t\t\treason: input.currentHashes.length < binding.sentCount ? \"history_rolled_back\" : \"sent_stream_diverged\",\n\t\t};\n\t}\n\tconst shared = commonPrefixLength(binding.sentHashes, input.currentHashes);\n\tif (shared === binding.sentCount) {\n\t\treturn {\n\t\t\tkind: \"reattach\",\n\t\t\tsdkSessionId: binding.sdkSessionId,\n\t\t\tfrom: binding.sentCount,\n\t\t\treason: drift ?? \"registry_miss\",\n\t\t};\n\t}\n\tif (!binding.lastAssistantUuid) return { kind: \"flatten\", reason: \"registry_miss\" };\n\treturn {\n\t\tkind: \"fork\",\n\t\tsdkSessionId: binding.sdkSessionId,\n\t\tatUuid: binding.lastAssistantUuid,\n\t\tfrom: shared,\n\t\treason: shared < binding.sentCount ? \"history_rolled_back\" : \"sent_stream_diverged\",\n\t};\n}\n\n/**\n * Resume-first: a live session is never abandoned for a flattened re-send. Only a\n * missing transcript, an unrecoverable boundary, a model identity drift, or account\n * drift on the config-dir lane on a persisted binding reaches `flatten`; every other\n * divergence resolves to `fork` (same lineage, new branch) or `reattach` (same\n * session, new query).\n */\nexport function decideNativeContinuity(input: ContinuityDecisionInput): ContinuityDecision {\n\treturn withRecordedInvalidation(decideFromState(input), input);\n}\n\nfunction decideFromState(input: ContinuityDecisionInput): ContinuityDecision {\n\tconst { entry, binding } = input;\n\tif (!entry) {\n\t\tif (!binding) return { kind: \"bootstrap\" };\n\t\treturn withoutUnconfirmedResume(decideFromBinding(input, binding), binding);\n\t}\n\n\tconst divergence = entry.pendingForkReason ?? entry.taintedReason;\n\tif (divergence) {\n\t\treturn forkOrFlatten(entry, entry.sentCount, PENDING_FORK_REASONS[divergence] ?? \"other\");\n\t}\n\n\tconst shared = commonPrefixLength(entry.sentHashes, input.currentHashes);\n\tif (shared < entry.sentCount) {\n\t\tconst rolledBack = input.currentHashes.length < entry.sentCount && shared === input.currentHashes.length;\n\t\treturn rolledBack\n\t\t\t? forkOrFlatten(entry, input.currentHashes.length, \"history_rolled_back\")\n\t\t\t: forkOrFlatten(entry, shared + 1, \"sent_stream_diverged\");\n\t}\n\n\tif (input.idleExpired) {\n\t\treturn { kind: \"reattach\", sdkSessionId: entry.sdkSessionId, from: entry.sentCount, reason: \"idle_ttl\" };\n\t}\n\n\tconst drift = identityDrift(input, entry);\n\tif (drift) {\n\t\treturn { kind: \"reattach\", sdkSessionId: entry.sdkSessionId, from: entry.sentCount, reason: drift };\n\t}\n\n\treturn { kind: \"delta\", from: entry.sentCount };\n}\n"]}
@@ -39,6 +39,8 @@ export declare function evaluateAbortOutcome(receipt: unknown): AbortOutcome;
39
39
  export declare function rememberBinding(binding: ContinuityBinding): void;
40
40
  export declare function getBinding(senpiSessionId: string): ContinuityBinding | undefined;
41
41
  export declare function forgetBinding(senpiSessionId: string): void;
42
+ export declare function rememberBindingInvalidation(senpiSessionId: string, reason: string | undefined): void;
43
+ export declare function bindingInvalidationReason(senpiSessionId: string): string | undefined;
42
44
  export declare function bindingFromEntry(entry: Pick<ClaudeSdkOauthSessionEntry, "senpiSessionId" | "sdkSessionId" | "sentCount" | "accountName" | "modelId" | "systemPromptHash" | "toolsetHash" | "assistantUuidByIndex" | "sdkSessionIdConfirmed">, sentHashes: readonly string[]): ContinuityBinding;
43
45
  export declare function verifyRestoredTranscript(binding: ContinuityBinding, cwd: string, authLane: ClaudeSdkOauthAuthLane): Promise<boolean>;
44
46
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"session-reattach.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,KAAK,0BAA0B,EAAoC,MAAM,uBAAuB,CAAC;AAG1G,MAAM,MAAM,iBAAiB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IAC9D;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAIF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAU/C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CAInE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAGhF;AAED,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,IAAI,CACV,0BAA0B,EACxB,gBAAgB,GAChB,cAAc,GACd,WAAW,GACX,aAAa,GACb,SAAS,GACT,kBAAkB,GAClB,aAAa,GACb,sBAAsB,GACtB,uBAAuB,CACzB,EACD,UAAU,EAAE,SAAS,MAAM,EAAE,GAC3B,iBAAiB,CAcnB;AAED,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,sBAAsB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAmBlB;AA6BD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4B/F"}
1
+ {"version":3,"file":"session-reattach.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,KAAK,0BAA0B,EAAoC,MAAM,uBAAuB,CAAC;AAG1G,MAAM,MAAM,iBAAiB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IAC9D;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAIF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAU/C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CAInE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAGhF;AAED,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAE1D;AAUD,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGpG;AAED,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEpF;AAED,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,IAAI,CACV,0BAA0B,EACxB,gBAAgB,GAChB,cAAc,GACd,WAAW,GACX,aAAa,GACb,SAAS,GACT,kBAAkB,GAClB,aAAa,GACb,sBAAsB,GACtB,uBAAuB,CACzB,EACD,UAAU,EAAE,SAAS,MAAM,EAAE,GAC3B,iBAAiB,CAcnB;AAED,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,sBAAsB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAmBlB;AA6BD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4B/F"}
@@ -25,6 +25,22 @@ export function getBinding(senpiSessionId) {
25
25
  export function forgetBinding(senpiSessionId) {
26
26
  bindings.delete(senpiSessionId);
27
27
  }
28
+ /**
29
+ * Reason the newest ledger record invalidated this session's binding. Held next
30
+ * to the binding it replaced so the next continuity decision can name the real
31
+ * cause instead of the no-record default: a restart re-reads it from the branch
32
+ * (session-registry-wiring), and a committed turn retires it with the marker.
33
+ */
34
+ const bindingInvalidations = new Map();
35
+ export function rememberBindingInvalidation(senpiSessionId, reason) {
36
+ if (reason === undefined)
37
+ bindingInvalidations.delete(senpiSessionId);
38
+ else
39
+ bindingInvalidations.set(senpiSessionId, reason);
40
+ }
41
+ export function bindingInvalidationReason(senpiSessionId) {
42
+ return bindingInvalidations.get(senpiSessionId);
43
+ }
28
44
  export function bindingFromEntry(entry, sentHashes) {
29
45
  return {
30
46
  senpiSessionId: entry.senpiSessionId,
@@ -1 +1 @@
1
- {"version":3,"file":"session-reattach.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClaudeSdkOauthAuthLane } from \"./options.ts\";\nimport type { Options, SessionMessage } from \"./sdk-boundary.ts\";\nimport { getSdkBoundary, loadClaudeAgentSdk } from \"./sdk-boundary.ts\";\nimport { type ClaudeSdkOauthSessionEntry, closeSession, getOrCreateSession } from \"./session-registry.ts\";\nimport { recordSyncedStream } from \"./session-sync.ts\";\n\nexport type ContinuityBinding = {\n\tsenpiSessionId: string;\n\tsdkSessionId: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tsentPrefixHash?: string;\n\t/**\n\t * False while the SDK has not yet acknowledged this session id (no init and\n\t * no replay echo): continuity must never resume or fork such an id.\n\t */\n\tsdkSessionIdConfirmed?: boolean;\n\tlastAssistantUuid: string | null;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\t/** Assistant boundaries kept as entries so a later fork still has a resume point. */\n\tassistantUuidByIndex?: readonly (readonly [number, string])[];\n\t/**\n\t * Digest of the FULL sent stream an attempt pushed but never got answered\n\t * (stream-start timeout abort/failure). Purely in-memory: it lets the SAME\n\t * turn's retry fork at the pre-turn boundary instead of re-appending its user\n\t * message to a lineage that already carries it. Never persisted — the sidecar\n\t * schema is fixed at schemaVersion 1 and restart retries are out of scope.\n\t */\n\tunansweredTurnDigest?: string;\n};\n\nexport type ReattachInput = {\n\tbinding: ContinuityBinding;\n\toptions: Options;\n\tatUuid?: string;\n\tsignal?: AbortSignal;\n};\n\nconst bindings = new Map<string, ContinuityBinding>();\n\nexport type AbortOutcome = \"keep\" | \"reattach\";\n\nfunction cloneBinding(binding: ContinuityBinding): ContinuityBinding {\n\treturn {\n\t\t...binding,\n\t\tsentHashes: [...binding.sentHashes],\n\t\tassistantUuidByIndex: binding.assistantUuidByIndex?.map(([index, uuid]) => [index, uuid]),\n\t};\n}\n\nexport function evaluateAbortOutcome(receipt: unknown): AbortOutcome {\n\tif (!receipt || typeof receipt !== \"object\") return \"reattach\";\n\tconst queued = (receipt as { still_queued?: unknown }).still_queued;\n\treturn Array.isArray(queued) && queued.length === 0 ? \"keep\" : \"reattach\";\n}\n\nexport function rememberBinding(binding: ContinuityBinding): void {\n\tbindings.set(binding.senpiSessionId, cloneBinding(binding));\n}\n\nexport function getBinding(senpiSessionId: string): ContinuityBinding | undefined {\n\tconst binding = bindings.get(senpiSessionId);\n\treturn binding ? cloneBinding(binding) : undefined;\n}\n\nexport function forgetBinding(senpiSessionId: string): void {\n\tbindings.delete(senpiSessionId);\n}\n\nexport function bindingFromEntry(\n\tentry: Pick<\n\t\tClaudeSdkOauthSessionEntry,\n\t\t| \"senpiSessionId\"\n\t\t| \"sdkSessionId\"\n\t\t| \"sentCount\"\n\t\t| \"accountName\"\n\t\t| \"modelId\"\n\t\t| \"systemPromptHash\"\n\t\t| \"toolsetHash\"\n\t\t| \"assistantUuidByIndex\"\n\t\t| \"sdkSessionIdConfirmed\"\n\t>,\n\tsentHashes: readonly string[],\n): ContinuityBinding {\n\treturn {\n\t\tsenpiSessionId: entry.senpiSessionId,\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tsdkSessionIdConfirmed: entry.sdkSessionIdConfirmed,\n\t\tsentCount: entry.sentCount,\n\t\tsentHashes: [...sentHashes],\n\t\tlastAssistantUuid: entry.assistantUuidByIndex.get(entry.sentCount) ?? null,\n\t\tassistantUuidByIndex: [...entry.assistantUuidByIndex.entries()],\n\t\taccountName: entry.accountName,\n\t\tmodelId: entry.modelId,\n\t\tsystemPromptHash: entry.systemPromptHash,\n\t\ttoolsetHash: entry.toolsetHash,\n\t};\n}\n\nexport async function verifyRestoredTranscript(\n\tbinding: ContinuityBinding,\n\tcwd: string,\n\tauthLane: ClaudeSdkOauthAuthLane,\n): Promise<boolean> {\n\tif (authLane === \"config-dir\") return false;\n\tlet messages: SessionMessage[];\n\ttry {\n\t\tmessages = await getSdkBoundary().getSessionMessages(binding.sdkSessionId, { dir: cwd });\n\t} catch (error) {\n\t\tif (error instanceof Error) return false;\n\t\tthrow error;\n\t}\n\tif (messages.length === 0 || messages.some((message) => message.session_id !== binding.sdkSessionId)) {\n\t\treturn false;\n\t}\n\tif (binding.lastAssistantUuid === null) return true;\n\treturn messages.some(\n\t\t(message) =>\n\t\t\tmessage.type === \"assistant\" &&\n\t\t\tmessage.uuid === binding.lastAssistantUuid &&\n\t\t\tmessage.parent_tool_use_id === null,\n\t);\n}\n\nasync function awaitInitialization(entry: ClaudeSdkOauthSessionEntry, signal?: AbortSignal): Promise<void> {\n\tconst initialize = entry.query.initializationResult;\n\tif (!initialize) return;\n\tif (!signal) {\n\t\tawait initialize.call(entry.query);\n\t\treturn;\n\t}\n\tlet rejectAborted: (reason?: unknown) => void = () => {};\n\tconst aborted = new Promise<never>((_, reject) => {\n\t\trejectAborted = reject;\n\t});\n\tconst onAbort = (): void => {\n\t\tcloseSession(entry.senpiSessionId, \"resume_initialization_aborted\");\n\t\trejectAborted(new Error(\"Claude SDK OAuth reattach aborted\"));\n\t};\n\tif (signal.aborted) {\n\t\tonAbort();\n\t} else {\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t}\n\ttry {\n\t\tawait Promise.race([initialize.call(entry.query), aborted]);\n\t} finally {\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\n/**\n * A new query is not a new session: `resume` re-attaches to the existing lineage.\n * `sessionId` must stay absent unless forking, because the SDK rejects the pair\n * (sdk.d.ts:1805-1808) and would otherwise silently start an unrelated session.\n */\nexport async function reattachSession(input: ReattachInput): Promise<ClaudeSdkOauthSessionEntry> {\n\t// getOrCreateSession() reaches the synchronous SDK `query` through the\n\t// session-registry boundary - see sdk-boundary.lazy.ts.\n\tawait loadClaudeAgentSdk();\n\tconst { binding, atUuid } = input;\n\tcloseSession(binding.senpiSessionId, \"reattach\");\n\tconst entry = getOrCreateSession({\n\t\tsenpiSessionId: binding.senpiSessionId,\n\t\taccountName: binding.accountName,\n\t\tmodelId: binding.modelId,\n\t\tsystemPromptHash: binding.systemPromptHash,\n\t\ttoolsetHash: binding.toolsetHash,\n\t\toptions: input.options,\n\t\tresume: atUuid ? { sdkSessionId: binding.sdkSessionId, atUuid } : { sdkSessionId: binding.sdkSessionId },\n\t});\n\n\ttry {\n\t\tawait awaitInitialization(entry, input.signal);\n\t} catch (error) {\n\t\tcloseSession(binding.senpiSessionId, \"resume_initialization_failed\");\n\t\tthrow error;\n\t}\n\n\trecordSyncedStream(entry, binding.sentHashes);\n\tfor (const [index, uuid] of binding.assistantUuidByIndex ?? []) entry.assistantUuidByIndex.set(index, uuid);\n\tif (binding.lastAssistantUuid) entry.assistantUuidByIndex.set(binding.sentCount, binding.lastAssistantUuid);\n\trememberBinding({ ...binding, sdkSessionId: entry.sdkSessionId });\n\treturn entry;\n}\n"]}
1
+ {"version":3,"file":"session-reattach.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClaudeSdkOauthAuthLane } from \"./options.ts\";\nimport type { Options, SessionMessage } from \"./sdk-boundary.ts\";\nimport { getSdkBoundary, loadClaudeAgentSdk } from \"./sdk-boundary.ts\";\nimport { type ClaudeSdkOauthSessionEntry, closeSession, getOrCreateSession } from \"./session-registry.ts\";\nimport { recordSyncedStream } from \"./session-sync.ts\";\n\nexport type ContinuityBinding = {\n\tsenpiSessionId: string;\n\tsdkSessionId: string;\n\tsentCount: number;\n\tsentHashes: readonly string[];\n\tsentPrefixHash?: string;\n\t/**\n\t * False while the SDK has not yet acknowledged this session id (no init and\n\t * no replay echo): continuity must never resume or fork such an id.\n\t */\n\tsdkSessionIdConfirmed?: boolean;\n\tlastAssistantUuid: string | null;\n\taccountName: string;\n\tmodelId: string;\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n\t/** Assistant boundaries kept as entries so a later fork still has a resume point. */\n\tassistantUuidByIndex?: readonly (readonly [number, string])[];\n\t/**\n\t * Digest of the FULL sent stream an attempt pushed but never got answered\n\t * (stream-start timeout abort/failure). Purely in-memory: it lets the SAME\n\t * turn's retry fork at the pre-turn boundary instead of re-appending its user\n\t * message to a lineage that already carries it. Never persisted — the sidecar\n\t * schema is fixed at schemaVersion 1 and restart retries are out of scope.\n\t */\n\tunansweredTurnDigest?: string;\n};\n\nexport type ReattachInput = {\n\tbinding: ContinuityBinding;\n\toptions: Options;\n\tatUuid?: string;\n\tsignal?: AbortSignal;\n};\n\nconst bindings = new Map<string, ContinuityBinding>();\n\nexport type AbortOutcome = \"keep\" | \"reattach\";\n\nfunction cloneBinding(binding: ContinuityBinding): ContinuityBinding {\n\treturn {\n\t\t...binding,\n\t\tsentHashes: [...binding.sentHashes],\n\t\tassistantUuidByIndex: binding.assistantUuidByIndex?.map(([index, uuid]) => [index, uuid]),\n\t};\n}\n\nexport function evaluateAbortOutcome(receipt: unknown): AbortOutcome {\n\tif (!receipt || typeof receipt !== \"object\") return \"reattach\";\n\tconst queued = (receipt as { still_queued?: unknown }).still_queued;\n\treturn Array.isArray(queued) && queued.length === 0 ? \"keep\" : \"reattach\";\n}\n\nexport function rememberBinding(binding: ContinuityBinding): void {\n\tbindings.set(binding.senpiSessionId, cloneBinding(binding));\n}\n\nexport function getBinding(senpiSessionId: string): ContinuityBinding | undefined {\n\tconst binding = bindings.get(senpiSessionId);\n\treturn binding ? cloneBinding(binding) : undefined;\n}\n\nexport function forgetBinding(senpiSessionId: string): void {\n\tbindings.delete(senpiSessionId);\n}\n\n/**\n * Reason the newest ledger record invalidated this session's binding. Held next\n * to the binding it replaced so the next continuity decision can name the real\n * cause instead of the no-record default: a restart re-reads it from the branch\n * (session-registry-wiring), and a committed turn retires it with the marker.\n */\nconst bindingInvalidations = new Map<string, string>();\n\nexport function rememberBindingInvalidation(senpiSessionId: string, reason: string | undefined): void {\n\tif (reason === undefined) bindingInvalidations.delete(senpiSessionId);\n\telse bindingInvalidations.set(senpiSessionId, reason);\n}\n\nexport function bindingInvalidationReason(senpiSessionId: string): string | undefined {\n\treturn bindingInvalidations.get(senpiSessionId);\n}\n\nexport function bindingFromEntry(\n\tentry: Pick<\n\t\tClaudeSdkOauthSessionEntry,\n\t\t| \"senpiSessionId\"\n\t\t| \"sdkSessionId\"\n\t\t| \"sentCount\"\n\t\t| \"accountName\"\n\t\t| \"modelId\"\n\t\t| \"systemPromptHash\"\n\t\t| \"toolsetHash\"\n\t\t| \"assistantUuidByIndex\"\n\t\t| \"sdkSessionIdConfirmed\"\n\t>,\n\tsentHashes: readonly string[],\n): ContinuityBinding {\n\treturn {\n\t\tsenpiSessionId: entry.senpiSessionId,\n\t\tsdkSessionId: entry.sdkSessionId,\n\t\tsdkSessionIdConfirmed: entry.sdkSessionIdConfirmed,\n\t\tsentCount: entry.sentCount,\n\t\tsentHashes: [...sentHashes],\n\t\tlastAssistantUuid: entry.assistantUuidByIndex.get(entry.sentCount) ?? null,\n\t\tassistantUuidByIndex: [...entry.assistantUuidByIndex.entries()],\n\t\taccountName: entry.accountName,\n\t\tmodelId: entry.modelId,\n\t\tsystemPromptHash: entry.systemPromptHash,\n\t\ttoolsetHash: entry.toolsetHash,\n\t};\n}\n\nexport async function verifyRestoredTranscript(\n\tbinding: ContinuityBinding,\n\tcwd: string,\n\tauthLane: ClaudeSdkOauthAuthLane,\n): Promise<boolean> {\n\tif (authLane === \"config-dir\") return false;\n\tlet messages: SessionMessage[];\n\ttry {\n\t\tmessages = await getSdkBoundary().getSessionMessages(binding.sdkSessionId, { dir: cwd });\n\t} catch (error) {\n\t\tif (error instanceof Error) return false;\n\t\tthrow error;\n\t}\n\tif (messages.length === 0 || messages.some((message) => message.session_id !== binding.sdkSessionId)) {\n\t\treturn false;\n\t}\n\tif (binding.lastAssistantUuid === null) return true;\n\treturn messages.some(\n\t\t(message) =>\n\t\t\tmessage.type === \"assistant\" &&\n\t\t\tmessage.uuid === binding.lastAssistantUuid &&\n\t\t\tmessage.parent_tool_use_id === null,\n\t);\n}\n\nasync function awaitInitialization(entry: ClaudeSdkOauthSessionEntry, signal?: AbortSignal): Promise<void> {\n\tconst initialize = entry.query.initializationResult;\n\tif (!initialize) return;\n\tif (!signal) {\n\t\tawait initialize.call(entry.query);\n\t\treturn;\n\t}\n\tlet rejectAborted: (reason?: unknown) => void = () => {};\n\tconst aborted = new Promise<never>((_, reject) => {\n\t\trejectAborted = reject;\n\t});\n\tconst onAbort = (): void => {\n\t\tcloseSession(entry.senpiSessionId, \"resume_initialization_aborted\");\n\t\trejectAborted(new Error(\"Claude SDK OAuth reattach aborted\"));\n\t};\n\tif (signal.aborted) {\n\t\tonAbort();\n\t} else {\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t}\n\ttry {\n\t\tawait Promise.race([initialize.call(entry.query), aborted]);\n\t} finally {\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\n/**\n * A new query is not a new session: `resume` re-attaches to the existing lineage.\n * `sessionId` must stay absent unless forking, because the SDK rejects the pair\n * (sdk.d.ts:1805-1808) and would otherwise silently start an unrelated session.\n */\nexport async function reattachSession(input: ReattachInput): Promise<ClaudeSdkOauthSessionEntry> {\n\t// getOrCreateSession() reaches the synchronous SDK `query` through the\n\t// session-registry boundary - see sdk-boundary.lazy.ts.\n\tawait loadClaudeAgentSdk();\n\tconst { binding, atUuid } = input;\n\tcloseSession(binding.senpiSessionId, \"reattach\");\n\tconst entry = getOrCreateSession({\n\t\tsenpiSessionId: binding.senpiSessionId,\n\t\taccountName: binding.accountName,\n\t\tmodelId: binding.modelId,\n\t\tsystemPromptHash: binding.systemPromptHash,\n\t\ttoolsetHash: binding.toolsetHash,\n\t\toptions: input.options,\n\t\tresume: atUuid ? { sdkSessionId: binding.sdkSessionId, atUuid } : { sdkSessionId: binding.sdkSessionId },\n\t});\n\n\ttry {\n\t\tawait awaitInitialization(entry, input.signal);\n\t} catch (error) {\n\t\tcloseSession(binding.senpiSessionId, \"resume_initialization_failed\");\n\t\tthrow error;\n\t}\n\n\trecordSyncedStream(entry, binding.sentHashes);\n\tfor (const [index, uuid] of binding.assistantUuidByIndex ?? []) entry.assistantUuidByIndex.set(index, uuid);\n\tif (binding.lastAssistantUuid) entry.assistantUuidByIndex.set(binding.sentCount, binding.lastAssistantUuid);\n\trememberBinding({ ...binding, sdkSessionId: entry.sdkSessionId });\n\treturn entry;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-registry-wiring.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAyDrE,wBAAgB,uBAAuB,CACtC,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,GACvE,IAAI,CAkHN"}
1
+ {"version":3,"file":"session-registry-wiring.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAqErE,wBAAgB,uBAAuB,CACtC,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,GACvE,IAAI,CA4HN"}
@@ -1,14 +1,15 @@
1
1
  import { convertToLlm } from "../../../messages.js";
2
2
  import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.js";
3
- import { BINDING_ENTRY_TYPE, BINDING_MARKER, bindingFromStoredBranch, storedBindingFromBinding, storedBindingFromEntry, } from "./session-binding.js";
3
+ import { BINDING_ENTRY_TYPE, BINDING_MARKER, bindingFromStoredBranch, invalidationReasonFromBranch, storedBindingFromBinding, storedBindingFromEntry, } from "./session-binding.js";
4
4
  import { deleteStoredBinding, readStoredBinding, writeStoredBinding } from "./session-binding-store.js";
5
5
  import { AssistantCommitBoundary, assistantContentHash, isResidentAssistant, isTerminalFailure, } from "./session-commit-boundary.js";
6
- import { bindingFromEntry, forgetBinding, getBinding, rememberBinding } from "./session-reattach.js";
6
+ import { bindingFromEntry, forgetBinding, getBinding, rememberBinding, rememberBindingInvalidation, } from "./session-reattach.js";
7
7
  import { closeSession, getSession, recordBranchInfo, recordPendingFork, switchSessionModel, } from "./session-registry.js";
8
8
  import { sentHashesForEntry, sentMessageHashes, sentMessages } from "./session-sync.js";
9
9
  const commitBoundary = new AssistantCommitBoundary();
10
- function persistBindingInvalidation(pi, reason) {
10
+ function persistBindingInvalidation(pi, sessionId, reason) {
11
11
  pi.appendEntry?.(BINDING_ENTRY_TYPE, { schemaVersion: 1, invalidated: true, reason });
12
+ rememberBindingInvalidation(sessionId, reason);
12
13
  }
13
14
  async function invalidateBinding(pi, ctx, reason) {
14
15
  const sessionId = ctx.sessionManager.getSessionId();
@@ -16,7 +17,7 @@ async function invalidateBinding(pi, ctx, reason) {
16
17
  const sessionFile = ctx.sessionManager.getSessionFile?.();
17
18
  if (sessionFile)
18
19
  await deleteStoredBinding(sessionFile);
19
- persistBindingInvalidation(pi, reason);
20
+ persistBindingInvalidation(pi, sessionId, reason);
20
21
  }
21
22
  function keepBindingThenClose(sessionId, reason) {
22
23
  const entry = getSession(sessionId);
@@ -36,17 +37,22 @@ export function registerSessionRegistry(pi) {
36
37
  return;
37
38
  const sessionId = ctx.sessionManager.getSessionId();
38
39
  forgetBinding(sessionId);
40
+ rememberBindingInvalidation(sessionId, undefined);
39
41
  const sessionFile = ctx.sessionManager.getSessionFile?.();
40
42
  if (event.reason === "new")
41
43
  return;
42
44
  if (event.reason === "fork") {
43
45
  if (sessionFile)
44
46
  await deleteStoredBinding(sessionFile);
45
- persistBindingInvalidation(pi, "fork");
47
+ persistBindingInvalidation(pi, sessionId, "fork");
46
48
  return;
47
49
  }
48
50
  if (!sessionFile)
49
51
  return;
52
+ // A restart carries the ledger, not the process maps: the newest binding record
53
+ // says whether a cause is still pending (invalidation) or was retired (marker).
54
+ const branch = ctx.sessionManager.getBranch();
55
+ rememberBindingInvalidation(sessionId, invalidationReasonFromBranch(branch));
50
56
  const stored = await readStoredBinding(sessionFile);
51
57
  if (!stored)
52
58
  return;
@@ -54,7 +60,7 @@ export function registerSessionRegistry(pi) {
54
60
  await deleteStoredBinding(sessionFile);
55
61
  return;
56
62
  }
57
- const binding = bindingFromStoredBranch(ctx.sessionManager.getBranch(), stored);
63
+ const binding = bindingFromStoredBranch(branch, stored);
58
64
  if (!binding) {
59
65
  await deleteStoredBinding(sessionFile);
60
66
  return;
@@ -78,9 +84,12 @@ export function registerSessionRegistry(pi) {
78
84
  });
79
85
  pi.on("model_select", async (event, ctx) => {
80
86
  const sessionId = ctx.sessionManager.getSessionId();
87
+ // Leaving this provider is an excursion, not an invalidation: the live SDK
88
+ // session closes but the binding stays, so coming back to the same model
89
+ // reattaches at the recorded prefix instead of re-sending the whole
90
+ // conversation (senpi#1747). Identity drift still flattens on the way back.
81
91
  if (event.model?.provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) {
82
- closeSession(sessionId, "model_selected");
83
- await invalidateBinding(pi, ctx, "model_selected");
92
+ keepBindingThenClose(sessionId, "model_selected");
84
93
  return;
85
94
  }
86
95
  if (!(await switchSessionModel(sessionId, event.model.id))) {
@@ -144,6 +153,8 @@ export function registerSessionRegistry(pi) {
144
153
  if (!recordFor("pending"))
145
154
  return;
146
155
  pi.appendEntry(BINDING_ENTRY_TYPE, BINDING_MARKER);
156
+ // The marker is now the newest ledger record, so any earlier cause is retired.
157
+ rememberBindingInvalidation(sessionId, undefined);
147
158
  const markerEntryId = ctx.sessionManager.getLeafId();
148
159
  if (!markerEntryId)
149
160
  return;