@code-yeongyu/senpi 2026.8.26 → 2026.8.28-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 (335) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +3 -3
  3. package/dist/cli/args.d.ts +5 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +18 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-services.d.ts.map +1 -1
  8. package/dist/core/agent-session-services.js +2 -0
  9. package/dist/core/agent-session-services.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +5 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +11 -1
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/auth-storage.d.ts +1 -0
  15. package/dist/core/auth-storage.d.ts.map +1 -1
  16. package/dist/core/auth-storage.js +3 -0
  17. package/dist/core/auth-storage.js.map +1 -1
  18. package/dist/core/bash-executor.d.ts.map +1 -1
  19. package/dist/core/bash-executor.js +67 -29
  20. package/dist/core/bash-executor.js.map +1 -1
  21. package/dist/core/credential-accounts.d.ts +31 -0
  22. package/dist/core/credential-accounts.d.ts.map +1 -0
  23. package/dist/core/credential-accounts.js +135 -0
  24. package/dist/core/credential-accounts.js.map +1 -0
  25. package/dist/core/credential-pool/classify.d.ts +47 -0
  26. package/dist/core/credential-pool/classify.d.ts.map +1 -0
  27. package/dist/core/credential-pool/classify.js +92 -0
  28. package/dist/core/credential-pool/classify.js.map +1 -0
  29. package/dist/core/credential-pool/env-slots.d.ts +22 -0
  30. package/dist/core/credential-pool/env-slots.d.ts.map +1 -0
  31. package/dist/core/credential-pool/env-slots.js +37 -0
  32. package/dist/core/credential-pool/env-slots.js.map +1 -0
  33. package/dist/core/credential-pool/failover.d.ts +62 -0
  34. package/dist/core/credential-pool/failover.d.ts.map +1 -0
  35. package/dist/core/credential-pool/failover.js +124 -0
  36. package/dist/core/credential-pool/failover.js.map +1 -0
  37. package/dist/core/credential-pool/rotation-stream.d.ts +54 -0
  38. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -0
  39. package/dist/core/credential-pool/rotation-stream.js +183 -0
  40. package/dist/core/credential-pool/rotation-stream.js.map +1 -0
  41. package/dist/core/credential-pool/state-store.d.ts +89 -0
  42. package/dist/core/credential-pool/state-store.d.ts.map +1 -0
  43. package/dist/core/credential-pool/state-store.js +166 -0
  44. package/dist/core/credential-pool/state-store.js.map +1 -0
  45. package/dist/core/extensions/builtin/account/index.d.ts +9 -0
  46. package/dist/core/extensions/builtin/account/index.d.ts.map +1 -0
  47. package/dist/core/extensions/builtin/account/index.js +69 -0
  48. package/dist/core/extensions/builtin/account/index.js.map +1 -0
  49. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/compaction/index.js +67 -0
  51. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  52. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  53. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/log.js +1 -0
  55. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  56. package/dist/core/extensions/builtin/config-reload/index.js +14 -22
  57. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  58. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  59. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  60. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  61. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  62. package/dist/core/extensions/builtin/goal/continuation.d.ts +8 -1
  63. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  64. package/dist/core/extensions/builtin/goal/continuation.js +15 -0
  65. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  66. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js +2 -2
  67. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js.map +1 -1
  68. package/dist/core/extensions/builtin/goal/format.d.ts +6 -1
  69. package/dist/core/extensions/builtin/goal/format.d.ts.map +1 -1
  70. package/dist/core/extensions/builtin/goal/format.js +6 -0
  71. package/dist/core/extensions/builtin/goal/format.js.map +1 -1
  72. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/goal/index.js +7 -0
  74. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  75. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +5 -2
  77. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  78. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  79. package/dist/core/extensions/builtin/goal/monitor-continuation.js +31 -7
  80. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  81. package/dist/core/extensions/builtin/goal/persistence.js +2 -0
  82. package/dist/core/extensions/builtin/goal/persistence.js.map +1 -1
  83. package/dist/core/extensions/builtin/goal/renderers.d.ts +14 -0
  84. package/dist/core/extensions/builtin/goal/renderers.d.ts.map +1 -0
  85. package/dist/core/extensions/builtin/goal/renderers.js +153 -0
  86. package/dist/core/extensions/builtin/goal/renderers.js.map +1 -0
  87. package/dist/core/extensions/builtin/goal/store.d.ts +6 -2
  88. package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
  89. package/dist/core/extensions/builtin/goal/store.js +8 -2
  90. package/dist/core/extensions/builtin/goal/store.js.map +1 -1
  91. package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
  92. package/dist/core/extensions/builtin/goal/tool-registration.js +16 -6
  93. package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
  94. package/dist/core/extensions/builtin/goal/types.d.ts +1 -0
  95. package/dist/core/extensions/builtin/goal/types.d.ts.map +1 -1
  96. package/dist/core/extensions/builtin/goal/types.js.map +1 -1
  97. package/dist/core/extensions/builtin/imagegen/index.d.ts.map +1 -1
  98. package/dist/core/extensions/builtin/imagegen/index.js +10 -3
  99. package/dist/core/extensions/builtin/imagegen/index.js.map +1 -1
  100. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  101. package/dist/core/extensions/builtin/index.js +4 -0
  102. package/dist/core/extensions/builtin/index.js.map +1 -1
  103. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  104. package/dist/core/extensions/builtin/service-tier.js +13 -0
  105. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  106. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  107. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  108. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  109. package/dist/core/extensions/builtin/todotools/index.js +1 -1
  110. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  111. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +1 -1
  112. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -1
  113. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +1 -1
  114. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -1
  115. package/dist/core/hidden-stdout-log.d.ts +10 -0
  116. package/dist/core/hidden-stdout-log.d.ts.map +1 -1
  117. package/dist/core/hidden-stdout-log.js +36 -5
  118. package/dist/core/hidden-stdout-log.js.map +1 -1
  119. package/dist/core/model-config-schema.d.ts +85 -12
  120. package/dist/core/model-config-schema.d.ts.map +1 -1
  121. package/dist/core/model-config-schema.js +25 -0
  122. package/dist/core/model-config-schema.js.map +1 -1
  123. package/dist/core/model-runtime.d.ts +36 -1
  124. package/dist/core/model-runtime.d.ts.map +1 -1
  125. package/dist/core/model-runtime.js +142 -12
  126. package/dist/core/model-runtime.js.map +1 -1
  127. package/dist/core/retry-fallback/chains.d.ts +23 -1
  128. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  129. package/dist/core/retry-fallback/chains.js +59 -3
  130. package/dist/core/retry-fallback/chains.js.map +1 -1
  131. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  132. package/dist/core/retry-fallback/controller.js +9 -2
  133. package/dist/core/retry-fallback/controller.js.map +1 -1
  134. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  135. package/dist/core/retry-fallback/settings.js +9 -0
  136. package/dist/core/retry-fallback/settings.js.map +1 -1
  137. package/dist/core/retry-fallback/validate.js +1 -1
  138. package/dist/core/retry-fallback/validate.js.map +1 -1
  139. package/dist/core/sdk.js +1 -1
  140. package/dist/core/sdk.js.map +1 -1
  141. package/dist/core/session-manager.d.ts +9 -0
  142. package/dist/core/session-manager.d.ts.map +1 -1
  143. package/dist/core/session-manager.js +31 -0
  144. package/dist/core/session-manager.js.map +1 -1
  145. package/dist/core/tools/output-accumulator.d.ts +2 -0
  146. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  147. package/dist/core/tools/output-accumulator.js +32 -7
  148. package/dist/core/tools/output-accumulator.js.map +1 -1
  149. package/dist/index.d.ts +1 -1
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +1 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/main.d.ts +7 -0
  154. package/dist/main.d.ts.map +1 -1
  155. package/dist/main.js +48 -4
  156. package/dist/main.js.map +1 -1
  157. package/dist/modes/app-server/server/account.d.ts.map +1 -1
  158. package/dist/modes/app-server/server/account.js +5 -5
  159. package/dist/modes/app-server/server/account.js.map +1 -1
  160. package/dist/modes/index.d.ts +1 -0
  161. package/dist/modes/index.d.ts.map +1 -1
  162. package/dist/modes/index.js +1 -0
  163. package/dist/modes/index.js.map +1 -1
  164. package/dist/modes/interactive/components/footer.d.ts +11 -3
  165. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  166. package/dist/modes/interactive/components/footer.js +31 -1
  167. package/dist/modes/interactive/components/footer.js.map +1 -1
  168. package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -1
  169. package/dist/modes/interactive/components/tool-execution-fallback.js +82 -1
  170. package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -1
  171. package/dist/modes/interactive/grok/chrome.d.ts +4 -4
  172. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  173. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  174. package/dist/modes/interactive/grok/footer.d.ts +3 -3
  175. package/dist/modes/interactive/grok/footer.d.ts.map +1 -1
  176. package/dist/modes/interactive/grok/footer.js.map +1 -1
  177. package/dist/modes/interactive/interactive-host-runtime.d.ts +44 -0
  178. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -0
  179. package/dist/modes/interactive/interactive-host-runtime.js +529 -0
  180. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -0
  181. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  182. package/dist/modes/interactive/interactive-mode.js +340 -257
  183. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  184. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  185. package/dist/modes/rpc/connection-handler.js +151 -30
  186. package/dist/modes/rpc/connection-handler.js.map +1 -1
  187. package/dist/modes/rpc/host-ensure.d.ts +41 -0
  188. package/dist/modes/rpc/host-ensure.d.ts.map +1 -0
  189. package/dist/modes/rpc/host-ensure.js +245 -0
  190. package/dist/modes/rpc/host-ensure.js.map +1 -0
  191. package/dist/modes/rpc/host-lifecycle.d.ts +57 -0
  192. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -0
  193. package/dist/modes/rpc/host-lifecycle.js +491 -0
  194. package/dist/modes/rpc/host-lifecycle.js.map +1 -0
  195. package/dist/modes/rpc/host-watchdog.d.ts +33 -0
  196. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -0
  197. package/dist/modes/rpc/host-watchdog.js +142 -0
  198. package/dist/modes/rpc/host-watchdog.js.map +1 -0
  199. package/dist/modes/rpc/multi-session-host.d.ts +7 -1
  200. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  201. package/dist/modes/rpc/multi-session-host.js +216 -32
  202. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  203. package/dist/modes/rpc/rpc-client.d.ts +115 -8
  204. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  205. package/dist/modes/rpc/rpc-client.js +200 -32
  206. package/dist/modes/rpc/rpc-client.js.map +1 -1
  207. package/dist/modes/rpc/rpc-mode.d.ts +4 -2
  208. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  209. package/dist/modes/rpc/rpc-mode.js +4 -2
  210. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  211. package/dist/modes/rpc/rpc-types.d.ts +166 -3
  212. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  213. package/dist/modes/rpc/rpc-types.js.map +1 -1
  214. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  215. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  216. package/dist/modes/rpc/session-command-router.js +165 -16
  217. package/dist/modes/rpc/session-command-router.js.map +1 -1
  218. package/dist/modes/rpc/session-event-writer.d.ts +15 -2
  219. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  220. package/dist/modes/rpc/session-event-writer.js +69 -25
  221. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  222. package/dist/modes/rpc/session-registry.d.ts +16 -0
  223. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  224. package/dist/modes/rpc/session-registry.js +40 -3
  225. package/dist/modes/rpc/session-registry.js.map +1 -1
  226. package/docs/providers.md +48 -0
  227. package/docs/rpc.md +96 -5
  228. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +56 -0
  229. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +13 -1
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +15 -1
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +711 -1
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +14 -1
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -10
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +9 -3
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +6 -1
  237. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
  238. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +20 -4
  239. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
  240. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +1 -1
  241. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -1
  242. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -1
  243. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts +21 -0
  244. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts.map +1 -0
  245. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js +27 -0
  246. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js.map +1 -0
  247. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +24 -0
  248. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -1
  249. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -1
  250. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -1
  251. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +7 -2
  252. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -1
  253. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  254. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js +46 -14
  257. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +11 -0
  259. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  260. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +30 -16
  261. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  262. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts +2 -1
  263. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js +2 -1
  265. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts +16 -0
  267. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts.map +1 -0
  268. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js +75 -0
  269. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js.map +1 -0
  270. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +45 -0
  271. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -0
  272. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +81 -0
  273. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -0
  274. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts +31 -0
  275. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts.map +1 -0
  276. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js +65 -0
  277. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js.map +1 -0
  278. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +12 -0
  279. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +37 -0
  281. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +6 -0
  283. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
  284. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +32 -6
  285. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +3 -2
  287. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +6 -0
  290. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +6 -1
  292. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +75 -0
  294. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +75 -0
  296. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  297. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  298. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  299. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  301. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  302. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  303. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  304. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  305. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  306. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
  307. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
  308. package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
  309. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  310. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  311. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  313. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  316. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  319. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  322. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  323. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +4 -0
  324. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  325. package/node_modules/@earendil-works/pi-ai/package.json +5 -5
  326. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  327. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  328. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +5 -0
  329. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  330. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +90 -1
  331. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  332. package/node_modules/@earendil-works/pi-tui/dist/tui.js +1 -1
  333. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  334. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  335. package/package.json +7 -7
@@ -1,3 +1,4 @@
1
+ import { VERSION } from "../../config.js";
1
2
  import { buildRpcSessionState } from "./connection-handler.js";
2
3
  import { RPC_ERROR_MISSING_SESSION_ID, RPC_ERROR_OPEN_FAILED, RPC_ERROR_SESSION_CLOSING, RPC_ERROR_UNKNOWN_SESSION, } from "./rpc-types.js";
3
4
  import { createRpcSessionBinding } from "./session-binding.js";
@@ -10,6 +11,12 @@ function error(id, command, code) {
10
11
  export class SessionCommandRouter {
11
12
  constructor(registry, writer, defaults, createBinding = createRpcSessionBinding, connectionOptions = {}) {
12
13
  this.bindings = new Map();
14
+ /** Session handles opened by each connection, with one count per attachment. */
15
+ this.sessionsByConnection = new Map();
16
+ /** Opens currently awaiting runtime/binding setup, keyed by connection. */
17
+ this.opensByConnection = new Map();
18
+ /** Connections whose socket has already been detached. */
19
+ this.releasedConnections = new Set();
13
20
  this.registry = registry;
14
21
  this.writer = writer;
15
22
  this.defaults = defaults;
@@ -18,12 +25,18 @@ export class SessionCommandRouter {
18
25
  }
19
26
  async handle(command) {
20
27
  if (command.type === "get_protocol_info") {
28
+ const capabilities = new Set(["multi_session", ...(this.connectionOptions?.capabilities ?? [])]);
21
29
  return {
22
30
  id: command.id,
23
31
  type: "response",
24
32
  command: "get_protocol_info",
25
33
  success: true,
26
- data: { protocolVersion: 1, capabilities: ["multi_session"], mode: "multi" },
34
+ data: {
35
+ protocolVersion: 1,
36
+ serverVersion: VERSION,
37
+ capabilities: [...capabilities],
38
+ mode: "multi",
39
+ },
27
40
  };
28
41
  }
29
42
  if (command.type === "list_sessions")
@@ -35,7 +48,7 @@ export class SessionCommandRouter {
35
48
  data: { sessions: this.registry.list() },
36
49
  };
37
50
  if (command.type === "open_session")
38
- return this.open(command);
51
+ return this.openWithBarrier(command);
39
52
  if (command.type === "close_session")
40
53
  return this.close(command);
41
54
  if (controls.has(command.type))
@@ -44,7 +57,10 @@ export class SessionCommandRouter {
44
57
  return error(command.id, command.type, RPC_ERROR_MISSING_SESSION_ID);
45
58
  try {
46
59
  this.registry.getForCommand(command.sessionId, command.type);
47
- await this.bindings.get(command.sessionId)?.handle(command);
60
+ const binding = this.bindings.get(command.sessionId);
61
+ if (!binding)
62
+ return error(command.id, command.type, RPC_ERROR_UNKNOWN_SESSION);
63
+ await binding.handle(command);
48
64
  return undefined;
49
65
  }
50
66
  catch (cause) {
@@ -53,23 +69,47 @@ export class SessionCommandRouter {
53
69
  }
54
70
  async dispose() {
55
71
  await Promise.all([...this.bindings.entries()].map(async ([sessionId, binding]) => {
72
+ let entry;
56
73
  try {
57
- this.registry.beginClose(sessionId);
74
+ entry = this.registry.beginClose(sessionId);
58
75
  }
59
76
  catch {
60
77
  return;
61
78
  }
62
79
  try {
80
+ if (entry.state === "open") {
81
+ while (entry.state === "open")
82
+ entry = this.registry.beginClose(sessionId);
83
+ }
63
84
  await binding.dispose();
64
85
  }
65
86
  finally {
66
87
  this.bindings.delete(sessionId);
67
- await this.registry.closeMarked(sessionId);
88
+ if (entry.state === "closing")
89
+ await this.registry.closeMarked(sessionId);
68
90
  }
69
91
  }));
70
92
  this.bindings.clear();
71
93
  }
72
- async open(command) {
94
+ openWithBarrier(command) {
95
+ const owner = this.writer.currentConnection();
96
+ if (owner === undefined)
97
+ return this.open(command);
98
+ let resolveBarrier;
99
+ const barrier = new Promise((resolve) => {
100
+ resolveBarrier = resolve;
101
+ });
102
+ const opens = this.opensByConnection.get(owner) ?? new Set();
103
+ opens.add(barrier);
104
+ this.opensByConnection.set(owner, opens);
105
+ return this.open(command, owner).finally(() => {
106
+ resolveBarrier();
107
+ opens.delete(barrier);
108
+ if (opens.size === 0)
109
+ this.opensByConnection.delete(owner);
110
+ });
111
+ }
112
+ async open(command, owner) {
73
113
  let opened;
74
114
  try {
75
115
  opened = await this.registry.openSession({
@@ -83,7 +123,20 @@ export class SessionCommandRouter {
83
123
  });
84
124
  const openedSession = opened;
85
125
  const entry = this.registry.getForCommand(openedSession.sessionId, "open_session");
86
- this.bindings.set(openedSession.sessionId, await this.createBinding(openedSession.sessionId, entry, this.writer, () => void this.close({ type: "close_session", sessionId: openedSession.sessionId }), this.connectionOptions));
126
+ // A client that dies without close_session (terminal closed, SIGKILL, dropped
127
+ // SSH) still holds this handle's attachment and its path reservation. Remember
128
+ // which connection owns it so releaseConnection() can close exactly that.
129
+ if (owner !== undefined) {
130
+ const owned = this.sessionsByConnection.get(owner) ?? new Map();
131
+ owned.set(openedSession.sessionId, (owned.get(openedSession.sessionId) ?? 0) + 1);
132
+ this.sessionsByConnection.set(owner, owned);
133
+ }
134
+ if (!this.bindings.has(openedSession.sessionId)) {
135
+ this.bindings.set(openedSession.sessionId, await this.createBinding(openedSession.sessionId, entry, this.writer, () => void this.close({ type: "close_session", sessionId: openedSession.sessionId }), this.connectionOptions));
136
+ }
137
+ if (owner !== undefined && this.releasedConnections.has(owner)) {
138
+ await this.releaseOwnedSession(openedSession.sessionId);
139
+ }
87
140
  const state = entry.runtime.session;
88
141
  this.writer.enqueue(opened.sessionId, {
89
142
  id: command.id,
@@ -93,6 +146,7 @@ export class SessionCommandRouter {
93
146
  data: {
94
147
  sessionId: opened.sessionId,
95
148
  state: buildRpcSessionState(state),
149
+ ...(opened.attached ? { attached: true } : {}),
96
150
  },
97
151
  });
98
152
  return undefined;
@@ -109,25 +163,120 @@ export class SessionCommandRouter {
109
163
  return error(command.id, "open_session", this.code(cause));
110
164
  }
111
165
  }
166
+ /**
167
+ * Releases every session a dropped connection still owned. Each handle goes through
168
+ * the same refcounted close as an explicit close_session, so a session another
169
+ * connection is still attached to keeps running and only the last owner tears it down.
170
+ */
171
+ async releaseConnection(connectionId) {
172
+ this.releasedConnections.add(connectionId);
173
+ const opens = this.opensByConnection.get(connectionId);
174
+ if (opens)
175
+ await Promise.all([...opens]);
176
+ const owned = this.sessionsByConnection.get(connectionId);
177
+ this.sessionsByConnection.delete(connectionId);
178
+ if (owned === undefined) {
179
+ this.releasedConnections.delete(connectionId);
180
+ return;
181
+ }
182
+ for (const [sessionId, count] of owned) {
183
+ for (let attachment = 0; attachment < count; attachment++) {
184
+ // Headless completion contract: a turn survives its client's death and
185
+ // runs to settlement (the host lifecycle keeps the process alive on
186
+ // active turns even with zero connections). Releasing mid-turn aborts
187
+ // the run and seals the session before agent_settled reaches the
188
+ // lifecycle observer, leaking the busy counter so the host never
189
+ // idle-exits. Defer - never skip - the release until the turn settles,
190
+ // so the dropped owner's reservation still frees afterwards.
191
+ const live = this.registry.peek(sessionId);
192
+ const session = live?.state === "open" ? live.runtime?.session : undefined;
193
+ if (session?.isStreaming) {
194
+ let released = false;
195
+ const unsubscribe = session.subscribe((event) => {
196
+ if (event.type !== "agent_settled" && event.type !== "agent_idle")
197
+ return;
198
+ if (released)
199
+ return;
200
+ released = true;
201
+ unsubscribe();
202
+ void this.releaseOwnedSession(sessionId).catch((cause) => {
203
+ process.stderr.write(`senpi rpc deferred release for session ${sessionId} failed: ${String(cause)}\n`);
204
+ });
205
+ });
206
+ continue;
207
+ }
208
+ await this.releaseOwnedSession(sessionId);
209
+ }
210
+ }
211
+ this.releasedConnections.delete(connectionId);
212
+ }
213
+ /**
214
+ * One owned handle's refcounted close: the same sequence as an explicit
215
+ * close_session, tolerant of races with other lifecycle paths (an entry
216
+ * already closed or claimed elsewhere is simply skipped). Disposal and
217
+ * binding removal happen only when this was the LAST attachment (the entry
218
+ * transitioned to "closing"): surviving attachments keep the shared binding
219
+ * and their event stream.
220
+ */
221
+ async releaseOwnedSession(sessionId) {
222
+ let entry;
223
+ try {
224
+ entry = this.registry.beginClose(sessionId);
225
+ }
226
+ catch {
227
+ // Already closed or claimed by another lifecycle path; nothing to release.
228
+ return;
229
+ }
230
+ try {
231
+ if (entry.state === "closing")
232
+ await this.bindings.get(sessionId)?.dispose();
233
+ }
234
+ finally {
235
+ if (entry.state === "closing") {
236
+ this.bindings.delete(sessionId);
237
+ await this.registry.closeMarked(sessionId);
238
+ }
239
+ }
240
+ }
112
241
  async close(command) {
113
242
  try {
114
243
  // This must be the first operation: binding.dispose() awaits teardown and
115
244
  // otherwise leaves a window where commands can enter the old handler.
116
- this.registry.beginClose(command.sessionId);
117
- try {
118
- await this.bindings.get(command.sessionId)?.dispose();
119
- }
120
- finally {
121
- this.bindings.delete(command.sessionId);
122
- await this.registry.closeMarked(command.sessionId);
245
+ const entry = this.registry.beginClose(command.sessionId);
246
+ const owner = this.writer.currentConnection();
247
+ if (owner !== undefined) {
248
+ const owned = this.sessionsByConnection.get(owner);
249
+ const count = owned?.get(command.sessionId);
250
+ if (owned && count !== undefined) {
251
+ if (count === 1)
252
+ owned.delete(command.sessionId);
253
+ else
254
+ owned.set(command.sessionId, count - 1);
255
+ if (owned.size === 0)
256
+ this.sessionsByConnection.delete(owner);
257
+ }
123
258
  }
124
- this.writer.closeSession(command.sessionId, {
259
+ const response = {
125
260
  id: command.id,
126
261
  type: "response",
127
262
  command: "close_session",
128
263
  success: true,
129
264
  data: {},
130
- });
265
+ };
266
+ try {
267
+ if (entry.state === "closing")
268
+ await this.bindings.get(command.sessionId)?.dispose();
269
+ }
270
+ finally {
271
+ if (entry.state === "closing") {
272
+ this.bindings.delete(command.sessionId);
273
+ await this.registry.closeMarked(command.sessionId);
274
+ this.writer.closeSession(command.sessionId, response);
275
+ }
276
+ else {
277
+ this.writer.enqueue(command.sessionId, response);
278
+ }
279
+ }
131
280
  return undefined;
132
281
  }
133
282
  catch (cause) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-command-router.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-command-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildRpcSessionState } from \"./connection-handler.ts\";\nimport type { RpcCommand, RpcResponse } from \"./rpc-types.ts\";\nimport {\n\tRPC_ERROR_MISSING_SESSION_ID,\n\tRPC_ERROR_OPEN_FAILED,\n\tRPC_ERROR_SESSION_CLOSING,\n\tRPC_ERROR_UNKNOWN_SESSION,\n} from \"./rpc-types.ts\";\nimport { createRpcSessionBinding, type RpcSessionBinding } from \"./session-binding.ts\";\nimport type { SessionEventWriter } from \"./session-event-writer.ts\";\nimport type { RpcSessionLaunchProfile, RpcSessionRegistry } from \"./session-registry.ts\";\nimport { RpcSessionRegistryError } from \"./session-registry.ts\";\n\nconst controls = new Set([\"get_protocol_info\", \"open_session\", \"close_session\", \"list_sessions\"]);\n\nfunction error(id: string | undefined, command: string, code: string): RpcResponse {\n\treturn { id, type: \"response\", command, success: false, error: code };\n}\n\n/** Routes control messages and enforces a routing handle for every session command. */\nexport class SessionCommandRouter {\n\tprivate readonly bindings = new Map<string, RpcSessionBinding>();\n\tprivate readonly registry: RpcSessionRegistry;\n\tprivate readonly writer: SessionEventWriter;\n\tprivate readonly defaults: Pick<\n\t\tRpcSessionLaunchProfile,\n\t\t\"cwd\" | \"permissionPreset\" | \"creationModel\" | \"initialThinkingLevel\"\n\t>;\n\tprivate readonly createBinding: typeof createRpcSessionBinding;\n\tprivate readonly connectionOptions: Parameters<typeof createRpcSessionBinding>[4];\n\n\tconstructor(\n\t\tregistry: RpcSessionRegistry,\n\t\twriter: SessionEventWriter,\n\t\tdefaults: Pick<RpcSessionLaunchProfile, \"cwd\" | \"permissionPreset\" | \"creationModel\" | \"initialThinkingLevel\">,\n\t\tcreateBinding: typeof createRpcSessionBinding = createRpcSessionBinding,\n\t\tconnectionOptions: Parameters<typeof createRpcSessionBinding>[4] = {},\n\t) {\n\t\tthis.registry = registry;\n\t\tthis.writer = writer;\n\t\tthis.defaults = defaults;\n\t\tthis.createBinding = createBinding;\n\t\tthis.connectionOptions = connectionOptions;\n\t}\n\n\tasync handle(command: RpcCommand): Promise<RpcResponse | undefined> {\n\t\tif (command.type === \"get_protocol_info\") {\n\t\t\treturn {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"get_protocol_info\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: { protocolVersion: 1, capabilities: [\"multi_session\"], mode: \"multi\" },\n\t\t\t};\n\t\t}\n\t\tif (command.type === \"list_sessions\")\n\t\t\treturn {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"list_sessions\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: { sessions: this.registry.list() },\n\t\t\t};\n\t\tif (command.type === \"open_session\") return this.open(command);\n\t\tif (command.type === \"close_session\") return this.close(command);\n\t\tif (controls.has(command.type)) return undefined;\n\t\tif (!command.sessionId) return error(command.id, command.type, RPC_ERROR_MISSING_SESSION_ID);\n\t\ttry {\n\t\t\tthis.registry.getForCommand(command.sessionId, command.type);\n\t\t\tawait this.bindings.get(command.sessionId)?.handle(command);\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\treturn error(command.id, command.type, this.code(cause));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t[...this.bindings.entries()].map(async ([sessionId, binding]) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.registry.beginClose(sessionId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait binding.dispose();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.bindings.delete(sessionId);\n\t\t\t\t\tawait this.registry.closeMarked(sessionId);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tthis.bindings.clear();\n\t}\n\n\tprivate async open(command: Extract<RpcCommand, { type: \"open_session\" }>): Promise<RpcResponse | undefined> {\n\t\tlet opened: { sessionId: string } | undefined;\n\t\ttry {\n\t\t\topened = await this.registry.openSession({\n\t\t\t\tcwd: command.cwd ?? this.defaults.cwd,\n\t\t\t\tsessionPath: command.sessionPath,\n\t\t\t\tpermissionPreset: command.permissionPreset ?? this.defaults.permissionPreset,\n\t\t\t\tcreationModel:\n\t\t\t\t\tcommand.provider && command.modelId\n\t\t\t\t\t\t? { provider: command.provider, modelId: command.modelId }\n\t\t\t\t\t\t: this.defaults.creationModel,\n\t\t\t\tinitialThinkingLevel: command.thinkingLevel ?? this.defaults.initialThinkingLevel,\n\t\t\t});\n\t\t\tconst openedSession = opened;\n\t\t\tconst entry = this.registry.getForCommand(openedSession.sessionId, \"open_session\");\n\t\t\tthis.bindings.set(\n\t\t\t\topenedSession.sessionId,\n\t\t\t\tawait this.createBinding(\n\t\t\t\t\topenedSession.sessionId,\n\t\t\t\t\tentry,\n\t\t\t\t\tthis.writer,\n\t\t\t\t\t() => void this.close({ type: \"close_session\", sessionId: openedSession.sessionId }),\n\t\t\t\t\tthis.connectionOptions,\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst state = entry.runtime!.session;\n\t\t\tthis.writer.enqueue(opened.sessionId, {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"open_session\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: {\n\t\t\t\t\tsessionId: opened.sessionId,\n\t\t\t\t\tstate: buildRpcSessionState(state),\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\tif (opened) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.registry.close(opened.sessionId);\n\t\t\t\t} catch {\n\t\t\t\t\t/* The open rollback has already removed the entry. */\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn error(command.id, \"open_session\", this.code(cause));\n\t\t}\n\t}\n\n\tprivate async close(command: Extract<RpcCommand, { type: \"close_session\" }>): Promise<RpcResponse | undefined> {\n\t\ttry {\n\t\t\t// This must be the first operation: binding.dispose() awaits teardown and\n\t\t\t// otherwise leaves a window where commands can enter the old handler.\n\t\t\tthis.registry.beginClose(command.sessionId);\n\t\t\ttry {\n\t\t\t\tawait this.bindings.get(command.sessionId)?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.bindings.delete(command.sessionId);\n\t\t\t\tawait this.registry.closeMarked(command.sessionId);\n\t\t\t}\n\t\t\tthis.writer.closeSession(command.sessionId, {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"close_session\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: {},\n\t\t\t});\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\treturn error(command.id, \"close_session\", this.code(cause));\n\t\t}\n\t}\n\n\tprivate code(cause: unknown): string {\n\t\tif (cause instanceof RpcSessionRegistryError) {\n\t\t\treturn cause.code === RPC_ERROR_OPEN_FAILED ? cause.message : cause.code;\n\t\t}\n\t\tif (cause instanceof Error && [RPC_ERROR_UNKNOWN_SESSION, RPC_ERROR_SESSION_CLOSING].includes(cause.message)) {\n\t\t\treturn cause.message;\n\t\t}\n\t\treturn cause instanceof Error && cause.message\n\t\t\t? `${RPC_ERROR_OPEN_FAILED}: ${cause.message}`\n\t\t\t: RPC_ERROR_UNKNOWN_SESSION;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-command-router.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-command-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { VERSION } from \"../../config.ts\";\nimport { buildRpcSessionState } from \"./connection-handler.ts\";\nimport type { RpcCommand, RpcResponse } from \"./rpc-types.ts\";\nimport {\n\tRPC_ERROR_MISSING_SESSION_ID,\n\tRPC_ERROR_OPEN_FAILED,\n\tRPC_ERROR_SESSION_CLOSING,\n\tRPC_ERROR_UNKNOWN_SESSION,\n} from \"./rpc-types.ts\";\nimport { createRpcSessionBinding, type RpcSessionBinding } from \"./session-binding.ts\";\nimport type { SessionEventWriter } from \"./session-event-writer.ts\";\nimport type {\n\tOpenRpcSession,\n\tRpcSessionEntry,\n\tRpcSessionLaunchProfile,\n\tRpcSessionRegistry,\n} from \"./session-registry.ts\";\nimport { RpcSessionRegistryError } from \"./session-registry.ts\";\n\nconst controls = new Set([\"get_protocol_info\", \"open_session\", \"close_session\", \"list_sessions\"]);\n\nfunction error(id: string | undefined, command: string, code: string): RpcResponse {\n\treturn { id, type: \"response\", command, success: false, error: code };\n}\n\n/** Routes control messages and enforces a routing handle for every session command. */\nexport class SessionCommandRouter {\n\tprivate readonly bindings = new Map<string, RpcSessionBinding>();\n\t/** Session handles opened by each connection, with one count per attachment. */\n\tprivate readonly sessionsByConnection = new Map<string, Map<string, number>>();\n\t/** Opens currently awaiting runtime/binding setup, keyed by connection. */\n\tprivate readonly opensByConnection = new Map<string, Set<Promise<void>>>();\n\t/** Connections whose socket has already been detached. */\n\tprivate readonly releasedConnections = new Set<string>();\n\tprivate readonly registry: RpcSessionRegistry;\n\tprivate readonly writer: SessionEventWriter;\n\tprivate readonly defaults: Pick<\n\t\tRpcSessionLaunchProfile,\n\t\t\"cwd\" | \"permissionPreset\" | \"creationModel\" | \"initialThinkingLevel\"\n\t>;\n\tprivate readonly createBinding: typeof createRpcSessionBinding;\n\tprivate readonly connectionOptions: Parameters<typeof createRpcSessionBinding>[4];\n\n\tconstructor(\n\t\tregistry: RpcSessionRegistry,\n\t\twriter: SessionEventWriter,\n\t\tdefaults: Pick<RpcSessionLaunchProfile, \"cwd\" | \"permissionPreset\" | \"creationModel\" | \"initialThinkingLevel\">,\n\t\tcreateBinding: typeof createRpcSessionBinding = createRpcSessionBinding,\n\t\tconnectionOptions: Parameters<typeof createRpcSessionBinding>[4] = {},\n\t) {\n\t\tthis.registry = registry;\n\t\tthis.writer = writer;\n\t\tthis.defaults = defaults;\n\t\tthis.createBinding = createBinding;\n\t\tthis.connectionOptions = connectionOptions;\n\t}\n\n\tasync handle(command: RpcCommand): Promise<RpcResponse | undefined> {\n\t\tif (command.type === \"get_protocol_info\") {\n\t\t\tconst capabilities = new Set([\"multi_session\", ...(this.connectionOptions?.capabilities ?? [])]);\n\t\t\treturn {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"get_protocol_info\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: {\n\t\t\t\t\tprotocolVersion: 1,\n\t\t\t\t\tserverVersion: VERSION,\n\t\t\t\t\tcapabilities: [...capabilities],\n\t\t\t\t\tmode: \"multi\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (command.type === \"list_sessions\")\n\t\t\treturn {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"list_sessions\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: { sessions: this.registry.list() },\n\t\t\t};\n\t\tif (command.type === \"open_session\") return this.openWithBarrier(command);\n\t\tif (command.type === \"close_session\") return this.close(command);\n\t\tif (controls.has(command.type)) return undefined;\n\t\tif (!command.sessionId) return error(command.id, command.type, RPC_ERROR_MISSING_SESSION_ID);\n\t\ttry {\n\t\t\tthis.registry.getForCommand(command.sessionId, command.type);\n\t\t\tconst binding = this.bindings.get(command.sessionId);\n\t\t\tif (!binding) return error(command.id, command.type, RPC_ERROR_UNKNOWN_SESSION);\n\t\t\tawait binding.handle(command);\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\treturn error(command.id, command.type, this.code(cause));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t[...this.bindings.entries()].map(async ([sessionId, binding]) => {\n\t\t\t\tlet entry: RpcSessionEntry | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tentry = this.registry.beginClose(sessionId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tif (entry.state === \"open\") {\n\t\t\t\t\t\twhile (entry.state === \"open\") entry = this.registry.beginClose(sessionId);\n\t\t\t\t\t}\n\t\t\t\t\tawait binding.dispose();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.bindings.delete(sessionId);\n\t\t\t\t\tif (entry.state === \"closing\") await this.registry.closeMarked(sessionId);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tthis.bindings.clear();\n\t}\n\n\tprivate openWithBarrier(command: Extract<RpcCommand, { type: \"open_session\" }>): Promise<RpcResponse | undefined> {\n\t\tconst owner = this.writer.currentConnection();\n\t\tif (owner === undefined) return this.open(command);\n\t\tlet resolveBarrier!: () => void;\n\t\tconst barrier = new Promise<void>((resolve) => {\n\t\t\tresolveBarrier = resolve;\n\t\t});\n\t\tconst opens = this.opensByConnection.get(owner) ?? new Set<Promise<void>>();\n\t\topens.add(barrier);\n\t\tthis.opensByConnection.set(owner, opens);\n\t\treturn this.open(command, owner).finally(() => {\n\t\t\tresolveBarrier();\n\t\t\topens.delete(barrier);\n\t\t\tif (opens.size === 0) this.opensByConnection.delete(owner);\n\t\t});\n\t}\n\n\tprivate async open(\n\t\tcommand: Extract<RpcCommand, { type: \"open_session\" }>,\n\t\towner?: string,\n\t): Promise<RpcResponse | undefined> {\n\t\tlet opened: OpenRpcSession | undefined;\n\t\ttry {\n\t\t\topened = await this.registry.openSession({\n\t\t\t\tcwd: command.cwd ?? this.defaults.cwd,\n\t\t\t\tsessionPath: command.sessionPath,\n\t\t\t\tpermissionPreset: command.permissionPreset ?? this.defaults.permissionPreset,\n\t\t\t\tcreationModel:\n\t\t\t\t\tcommand.provider && command.modelId\n\t\t\t\t\t\t? { provider: command.provider, modelId: command.modelId }\n\t\t\t\t\t\t: this.defaults.creationModel,\n\t\t\t\tinitialThinkingLevel: command.thinkingLevel ?? this.defaults.initialThinkingLevel,\n\t\t\t});\n\t\t\tconst openedSession = opened;\n\t\t\tconst entry = this.registry.getForCommand(openedSession.sessionId, \"open_session\");\n\t\t\t// A client that dies without close_session (terminal closed, SIGKILL, dropped\n\t\t\t// SSH) still holds this handle's attachment and its path reservation. Remember\n\t\t\t// which connection owns it so releaseConnection() can close exactly that.\n\t\t\tif (owner !== undefined) {\n\t\t\t\tconst owned = this.sessionsByConnection.get(owner) ?? new Map<string, number>();\n\t\t\t\towned.set(openedSession.sessionId, (owned.get(openedSession.sessionId) ?? 0) + 1);\n\t\t\t\tthis.sessionsByConnection.set(owner, owned);\n\t\t\t}\n\t\t\tif (!this.bindings.has(openedSession.sessionId)) {\n\t\t\t\tthis.bindings.set(\n\t\t\t\t\topenedSession.sessionId,\n\t\t\t\t\tawait this.createBinding(\n\t\t\t\t\t\topenedSession.sessionId,\n\t\t\t\t\t\tentry,\n\t\t\t\t\t\tthis.writer,\n\t\t\t\t\t\t() => void this.close({ type: \"close_session\", sessionId: openedSession.sessionId }),\n\t\t\t\t\t\tthis.connectionOptions,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (owner !== undefined && this.releasedConnections.has(owner)) {\n\t\t\t\tawait this.releaseOwnedSession(openedSession.sessionId);\n\t\t\t}\n\t\t\tconst state = entry.runtime!.session;\n\t\t\tthis.writer.enqueue(opened.sessionId, {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\",\n\t\t\t\tcommand: \"open_session\",\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: {\n\t\t\t\t\tsessionId: opened.sessionId,\n\t\t\t\t\tstate: buildRpcSessionState(state),\n\t\t\t\t\t...(opened.attached ? { attached: true } : {}),\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\tif (opened) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.registry.close(opened.sessionId);\n\t\t\t\t} catch {\n\t\t\t\t\t/* The open rollback has already removed the entry. */\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn error(command.id, \"open_session\", this.code(cause));\n\t\t}\n\t}\n\n\t/**\n\t * Releases every session a dropped connection still owned. Each handle goes through\n\t * the same refcounted close as an explicit close_session, so a session another\n\t * connection is still attached to keeps running and only the last owner tears it down.\n\t */\n\tasync releaseConnection(connectionId: string): Promise<void> {\n\t\tthis.releasedConnections.add(connectionId);\n\t\tconst opens = this.opensByConnection.get(connectionId);\n\t\tif (opens) await Promise.all([...opens]);\n\t\tconst owned = this.sessionsByConnection.get(connectionId);\n\t\tthis.sessionsByConnection.delete(connectionId);\n\t\tif (owned === undefined) {\n\t\t\tthis.releasedConnections.delete(connectionId);\n\t\t\treturn;\n\t\t}\n\t\tfor (const [sessionId, count] of owned) {\n\t\t\tfor (let attachment = 0; attachment < count; attachment++) {\n\t\t\t\t// Headless completion contract: a turn survives its client's death and\n\t\t\t\t// runs to settlement (the host lifecycle keeps the process alive on\n\t\t\t\t// active turns even with zero connections). Releasing mid-turn aborts\n\t\t\t\t// the run and seals the session before agent_settled reaches the\n\t\t\t\t// lifecycle observer, leaking the busy counter so the host never\n\t\t\t\t// idle-exits. Defer - never skip - the release until the turn settles,\n\t\t\t\t// so the dropped owner's reservation still frees afterwards.\n\t\t\t\tconst live = this.registry.peek(sessionId);\n\t\t\t\tconst session = live?.state === \"open\" ? live.runtime?.session : undefined;\n\t\t\t\tif (session?.isStreaming) {\n\t\t\t\t\tlet released = false;\n\t\t\t\t\tconst unsubscribe = session.subscribe((event) => {\n\t\t\t\t\t\tif (event.type !== \"agent_settled\" && event.type !== \"agent_idle\") return;\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\treleased = true;\n\t\t\t\t\t\tunsubscribe();\n\t\t\t\t\t\tvoid this.releaseOwnedSession(sessionId).catch((cause) => {\n\t\t\t\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t\t\t\t`senpi rpc deferred release for session ${sessionId} failed: ${String(cause)}\\n`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait this.releaseOwnedSession(sessionId);\n\t\t\t}\n\t\t}\n\t\tthis.releasedConnections.delete(connectionId);\n\t}\n\n\t/**\n\t * One owned handle's refcounted close: the same sequence as an explicit\n\t * close_session, tolerant of races with other lifecycle paths (an entry\n\t * already closed or claimed elsewhere is simply skipped). Disposal and\n\t * binding removal happen only when this was the LAST attachment (the entry\n\t * transitioned to \"closing\"): surviving attachments keep the shared binding\n\t * and their event stream.\n\t */\n\tprivate async releaseOwnedSession(sessionId: string): Promise<void> {\n\t\tlet entry: RpcSessionEntry | undefined;\n\t\ttry {\n\t\t\tentry = this.registry.beginClose(sessionId);\n\t\t} catch {\n\t\t\t// Already closed or claimed by another lifecycle path; nothing to release.\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tif (entry.state === \"closing\") await this.bindings.get(sessionId)?.dispose();\n\t\t} finally {\n\t\t\tif (entry.state === \"closing\") {\n\t\t\t\tthis.bindings.delete(sessionId);\n\t\t\t\tawait this.registry.closeMarked(sessionId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async close(command: Extract<RpcCommand, { type: \"close_session\" }>): Promise<RpcResponse | undefined> {\n\t\ttry {\n\t\t\t// This must be the first operation: binding.dispose() awaits teardown and\n\t\t\t// otherwise leaves a window where commands can enter the old handler.\n\t\t\tconst entry = this.registry.beginClose(command.sessionId);\n\t\t\tconst owner = this.writer.currentConnection();\n\t\t\tif (owner !== undefined) {\n\t\t\t\tconst owned = this.sessionsByConnection.get(owner);\n\t\t\t\tconst count = owned?.get(command.sessionId);\n\t\t\t\tif (owned && count !== undefined) {\n\t\t\t\t\tif (count === 1) owned.delete(command.sessionId);\n\t\t\t\t\telse owned.set(command.sessionId, count - 1);\n\t\t\t\t\tif (owned.size === 0) this.sessionsByConnection.delete(owner);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst response = {\n\t\t\t\tid: command.id,\n\t\t\t\ttype: \"response\" as const,\n\t\t\t\tcommand: \"close_session\" as const,\n\t\t\t\tsuccess: true as const,\n\t\t\t\tdata: {},\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tif (entry.state === \"closing\") await this.bindings.get(command.sessionId)?.dispose();\n\t\t\t} finally {\n\t\t\t\tif (entry.state === \"closing\") {\n\t\t\t\t\tthis.bindings.delete(command.sessionId);\n\t\t\t\t\tawait this.registry.closeMarked(command.sessionId);\n\t\t\t\t\tthis.writer.closeSession(command.sessionId, response);\n\t\t\t\t} else {\n\t\t\t\t\tthis.writer.enqueue(command.sessionId, response);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn undefined;\n\t\t} catch (cause) {\n\t\t\treturn error(command.id, \"close_session\", this.code(cause));\n\t\t}\n\t}\n\n\tprivate code(cause: unknown): string {\n\t\tif (cause instanceof RpcSessionRegistryError) {\n\t\t\treturn cause.code === RPC_ERROR_OPEN_FAILED ? cause.message : cause.code;\n\t\t}\n\t\tif (cause instanceof Error && [RPC_ERROR_UNKNOWN_SESSION, RPC_ERROR_SESSION_CLOSING].includes(cause.message)) {\n\t\t\treturn cause.message;\n\t\t}\n\t\treturn cause instanceof Error && cause.message\n\t\t\t? `${RPC_ERROR_OPEN_FAILED}: ${cause.message}`\n\t\t\t: RPC_ERROR_UNKNOWN_SESSION;\n\t}\n}\n"]}
@@ -10,8 +10,14 @@ type FlushScheduler = (flush: () => Promise<void>) => void;
10
10
  * by itself: coalescing records from different sessions would obscure the
11
11
  * scheduling boundary and violate D9.
12
12
  */
13
+ export interface SessionEventWriterConnection {
14
+ readonly writeRaw: RawWriter;
15
+ readonly waitForBackpressure: BackpressureWaiter;
16
+ }
13
17
  export declare class SessionEventWriter {
14
18
  private readonly queues;
19
+ private readonly connections;
20
+ private readonly connectionContext;
15
21
  private readonly controlQueue;
16
22
  private readonly readyQueues;
17
23
  private readonly sealedSessions;
@@ -26,9 +32,15 @@ export declare class SessionEventWriter {
26
32
  constructor(writeRaw: RawWriter, waitForBackpressure: BackpressureWaiter, scheduleFlush?: FlushScheduler);
27
33
  get bufferedRecordCount(): number;
28
34
  get bufferedByteLength(): number;
29
- /** Queue one session-owned response, event, or extension UI request. */
35
+ registerConnection(id: string, connection: SessionEventWriterConnection): void;
36
+ unregisterConnection(id: string): void;
37
+ /** Execute a connection's command with its response destination in context. */
38
+ withConnection<T>(id: string, task: () => T): T;
39
+ /** Connection id of the command currently being dispatched, when one owns it. */
40
+ currentConnection(): string | undefined;
41
+ /** Queue a session record. Lifecycle/events are broadcast; responses/UI are targeted. */
30
42
  enqueue(sessionId: string, value: object): boolean;
31
- /** Queue one untagged host-control response without compaction. */
43
+ /** Queue one untagged host-control response for the current connection. */
32
44
  enqueueControl(value: object): Promise<void>;
33
45
  /**
34
46
  * Prevent subsequent records for a session and append its terminal response.
@@ -40,6 +52,7 @@ export declare class SessionEventWriter {
40
52
  flush(): Promise<void>;
41
53
  private drainUntilEmpty;
42
54
  private drainReadyQueues;
55
+ private connectionQueue;
43
56
  private appendSessionRecord;
44
57
  private demoteAndMerge;
45
58
  private append;
@@ -1 +1 @@
1
- {"version":3,"file":"session-event-writer.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"AAEA,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AACzC,KAAK,kBAAkB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAC9C,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAkD3D;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAqB;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,CAA0C;IAC3D,OAAO,CAAC,OAAO,CAAC,CAAU;gBAEd,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,cAAc;gBACnD,QAAQ,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,cAAc;IAexG,IAAI,mBAAmB,IAAI,MAAM,CAMhC;IAED,IAAI,kBAAkB,IAAI,MAAM,CAQ/B;IAED,wEAAwE;IACxE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAOlD,mEAAmE;IACnE,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5C;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOvD,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBR,eAAe;YAMf,gBAAgB;IA8B9B,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,cAAc;IA2BtB,OAAO,CAAC,MAAM;IAcd,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAE,SAAS;CAIlB"}
1
+ {"version":3,"file":"session-event-writer.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"AAGA,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AACzC,KAAK,kBAAkB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAC9C,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAmD3D;;;;;;;;GAQG;AACH,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,kBAAkB,CAAC;CACjD;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmD;IAC/E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmC;IACrE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAqB;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,CAA0C;IAC3D,OAAO,CAAC,OAAO,CAAC,CAAU;gBAEd,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,cAAc;gBACnD,QAAQ,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,cAAc;IAexG,IAAI,mBAAmB,IAAI,MAAM,CAMhC;IAED,IAAI,kBAAkB,IAAI,MAAM,CAQ/B;IAED,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,4BAA4B,GAAG,IAAI;IAI9E,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAItC,+EAA+E;IAC/E,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC;IAI/C,iFAAiF;IACjF,iBAAiB,IAAI,MAAM,GAAG,SAAS;IAIvC,yFAAyF;IACzF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAclD,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYvD,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBR,eAAe;YAMf,gBAAgB;IAuC9B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,cAAc;IA2BtB,OAAO,CAAC,MAAM;IAcd,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAE,SAAS;CAIlB"}
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { serializeJsonLine } from "./jsonl.js";
2
3
  const MESSAGE_KEY = "message";
3
4
  const COMPACT_DELTA_TYPES = new Set(["text_delta", "thinking_delta", "toolcall_delta"]);
@@ -25,18 +26,11 @@ function toolUpdateKey(value) {
25
26
  ? `tool:${value.toolCallId}`
26
27
  : undefined;
27
28
  }
28
- /**
29
- * Process-wide stdout scheduler for multi-session RPC mode.
30
- *
31
- * Each queue contains complete structured JSONL records for one routing handle.
32
- * Draining takes one record per queue in round-robin order and waits for stdout
33
- * backpressure before selecting the next one. A record is deliberately written
34
- * by itself: coalescing records from different sessions would obscure the
35
- * scheduling boundary and violate D9.
36
- */
37
29
  export class SessionEventWriter {
38
30
  constructor(writeRaw, waitOrSchedule, scheduleFlush) {
39
31
  this.queues = new Map();
32
+ this.connections = new Map();
33
+ this.connectionContext = new AsyncLocalStorage();
40
34
  this.controlQueue = { latestByKey: new Map(), ready: false };
41
35
  this.readyQueues = [];
42
36
  this.sealedSessions = new Set();
@@ -67,22 +61,46 @@ export class SessionEventWriter {
67
61
  }
68
62
  return bytes;
69
63
  }
70
- /** Queue one session-owned response, event, or extension UI request. */
64
+ registerConnection(id, connection) {
65
+ this.connections.set(id, connection);
66
+ }
67
+ unregisterConnection(id) {
68
+ this.connections.delete(id);
69
+ }
70
+ /** Execute a connection's command with its response destination in context. */
71
+ withConnection(id, task) {
72
+ return this.connectionContext.run(id, task);
73
+ }
74
+ /** Connection id of the command currently being dispatched, when one owns it. */
75
+ currentConnection() {
76
+ return this.connectionContext.getStore();
77
+ }
78
+ /** Queue a session record. Lifecycle/events are broadcast; responses/UI are targeted. */
71
79
  enqueue(sessionId, value) {
72
80
  if (this.sealedSessions.has(sessionId))
73
81
  return false;
74
- this.appendSessionRecord(sessionId, { ...value, sessionId });
82
+ const targetId = this.connectionContext.getStore();
83
+ const record = value;
84
+ const isTargeted = record.type === "response" || record.type === "extension_ui_request";
85
+ const targets = isTargeted ? [targetId] : this.connections.size > 0 ? [...this.connections.keys()] : [undefined];
86
+ for (const target of targets) {
87
+ if (target !== undefined && !this.connections.has(target))
88
+ continue;
89
+ this.appendSessionRecord(sessionId, { ...value, sessionId }, target);
90
+ }
75
91
  this.requestFlush();
76
92
  return true;
77
93
  }
78
- /** Queue one untagged host-control response without compaction. */
94
+ /** Queue one untagged host-control response for the current connection. */
79
95
  enqueueControl(value) {
80
96
  if (this.failure !== undefined)
81
97
  return Promise.reject(this.failure);
98
+ const targetId = this.connectionContext.getStore();
99
+ const queue = targetId === undefined ? this.controlQueue : this.connectionQueue(targetId);
82
100
  const completion = new Promise((resolve, reject) => {
83
- this.append(this.controlQueue, { ...value }, undefined, resolve, reject);
101
+ this.append(queue, { ...value }, undefined, resolve, reject);
84
102
  });
85
- this.markReady(this.controlQueue);
103
+ this.markReady(queue);
86
104
  this.requestFlush();
87
105
  return completion;
88
106
  }
@@ -95,7 +113,12 @@ export class SessionEventWriter {
95
113
  if (this.sealedSessions.has(sessionId))
96
114
  return;
97
115
  this.sealedSessions.add(sessionId);
98
- this.appendSessionRecord(sessionId, { ...response, sessionId });
116
+ const targetId = this.connectionContext.getStore();
117
+ const lifecycle = { type: "session_closed", sessionId };
118
+ for (const connectionId of this.connections.keys()) {
119
+ this.appendSessionRecord(sessionId, lifecycle, connectionId);
120
+ }
121
+ this.appendSessionRecord(sessionId, { ...response, sessionId }, targetId);
99
122
  this.requestFlush();
100
123
  }
101
124
  /** Drain every retained lane and the current in-flight record. */
@@ -142,10 +165,18 @@ export class SessionEventWriter {
142
165
  try {
143
166
  // D9: exactly one complete record per raw write. The next lane is not
144
167
  // selected until this record has cleared stdout backpressure.
145
- this.writeRaw(serializeJsonLine(node.value));
146
- if (this.waitForBackpressure)
147
- await this.waitForBackpressure();
148
- node.resolve?.();
168
+ const connection = queue.targetId ? this.connections.get(queue.targetId) : undefined;
169
+ const writeRaw = connection?.writeRaw ?? this.writeRaw;
170
+ const waitForBackpressure = connection?.waitForBackpressure ?? this.waitForBackpressure;
171
+ if (!connection && queue.targetId) {
172
+ node.resolve?.();
173
+ }
174
+ else {
175
+ writeRaw(serializeJsonLine(node.value));
176
+ if (waitForBackpressure)
177
+ await waitForBackpressure();
178
+ node.resolve?.();
179
+ }
149
180
  }
150
181
  catch (cause) {
151
182
  node.reject?.(cause);
@@ -157,16 +188,29 @@ export class SessionEventWriter {
157
188
  if (queue.head) {
158
189
  this.markReady(queue);
159
190
  }
160
- else if (queue.sessionId) {
161
- this.queues.delete(queue.sessionId);
191
+ else if (queue.sessionId || queue.targetId) {
192
+ for (const [key, candidate] of this.queues) {
193
+ if (candidate === queue)
194
+ this.queues.delete(key);
195
+ }
162
196
  }
163
197
  }
164
198
  }
165
- appendSessionRecord(sessionId, value) {
166
- let queue = this.queues.get(sessionId);
199
+ connectionQueue(targetId) {
200
+ const key = `connection:${targetId}`;
201
+ let queue = this.queues.get(key);
202
+ if (!queue) {
203
+ queue = { targetId, latestByKey: new Map(), ready: false };
204
+ this.queues.set(key, queue);
205
+ }
206
+ return queue;
207
+ }
208
+ appendSessionRecord(sessionId, value, targetId) {
209
+ const key = `${targetId ?? "default"}:${sessionId}`;
210
+ let queue = this.queues.get(key);
167
211
  if (!queue) {
168
- queue = { sessionId, latestByKey: new Map(), ready: false };
169
- this.queues.set(sessionId, queue);
212
+ queue = { sessionId, targetId, latestByKey: new Map(), ready: false };
213
+ this.queues.set(key, queue);
170
214
  }
171
215
  const delta = compactDelta(value);
172
216
  if (delta) {