@code-yeongyu/senpi 2026.9.21-2 → 2026.9.22-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 (379) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/bundle/chunks/{anthropic-messages-C3ZY2ANT.js → anthropic-messages-2P7APOOQ.js} +1 -1
  3. package/dist/bundle/chunks/{app-server-command-HSX5RFAP.js → app-server-command-PRNA6QQU.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-7TAGS2VU.js → azure-openai-responses-3AH7MJWF.js} +1 -1
  5. package/dist/bundle/chunks/{bun-extension-importer-UO7V2MZ7.js → bun-extension-importer-H4IJCWWQ.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-OSDJ2SYO.js → chunk-5XC2FNJ5.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-D5CL7PPD.js → chunk-64JQFFHH.js} +2 -2
  8. package/dist/bundle/chunks/{chunk-Z3KQB2R3.js → chunk-6OD4TT33.js} +95 -39
  9. package/dist/bundle/chunks/{chunk-NEEAKXDT.js → chunk-7EH64T6E.js} +2 -2
  10. package/dist/bundle/chunks/{chunk-MIVOQT3Q.js → chunk-ARJJU3MS.js} +1 -1
  11. package/dist/bundle/chunks/chunk-BOZ37BOT.js +14 -0
  12. package/dist/bundle/chunks/{chunk-POMPEGWQ.js → chunk-F3DXL5MX.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-GLKVDRNP.js → chunk-GXUYWMN2.js} +1 -1
  14. package/dist/bundle/chunks/chunk-HH3KJDWA.js +2 -0
  15. package/dist/bundle/chunks/{chunk-B7W62BM7.js → chunk-IPRJIKLL.js} +1 -1
  16. package/dist/bundle/chunks/chunk-JEFYJDHP.js +8 -0
  17. package/dist/bundle/chunks/{chunk-T6XA2IZR.js → chunk-JR4X6ECO.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-I425CMTO.js → chunk-JZ6YQU3A.js} +4 -4
  19. package/dist/bundle/chunks/{chunk-KNM6SRPE.js → chunk-K3HEWWSS.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-M2XAY6I7.js → chunk-M2HSO7MH.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-UYVPLEOL.js → chunk-NK2RNX25.js} +1 -1
  22. package/dist/bundle/chunks/chunk-NMB4C6ZZ.js +2 -0
  23. package/dist/bundle/chunks/{chunk-VEO3ABZR.js → chunk-OLJ7LQJY.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-GVI2HP7N.js → chunk-OPNVT7BU.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-XHMOFY4T.js → chunk-R6LVC7SP.js} +4 -4
  26. package/dist/bundle/chunks/{chunk-3SLCHXZO.js → chunk-UCPWK6TZ.js} +2 -2
  27. package/dist/bundle/chunks/{chunk-B2IM56LB.js → chunk-VYIJQQ5I.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-47GF6XYZ.js → chunk-YT2ZSZOY.js} +1 -1
  29. package/dist/bundle/chunks/{cli-main-GQTEEZ2V.js → cli-main-AFJNXUSL.js} +1 -1
  30. package/dist/bundle/chunks/github-copilot.js +1 -1
  31. package/dist/bundle/chunks/{google-generative-ai-JWIH75VF.js → google-generative-ai-Y2IYMUVC.js} +1 -1
  32. package/dist/bundle/chunks/{google-vertex-IRGAY5XR.js → google-vertex-TKBBMPHV.js} +1 -1
  33. package/dist/bundle/chunks/{host-command-KOEKIHIZ.js → host-command-OVA6GEWC.js} +1 -1
  34. package/dist/bundle/chunks/host-lifecycle-IDQPLYEA.js +3 -0
  35. package/dist/bundle/chunks/host-lifecycle.js +10 -6
  36. package/dist/bundle/chunks/{interactive-host-runtime-2KTFEW7O.js → interactive-host-runtime-F5L4USLR.js} +1 -1
  37. package/dist/bundle/chunks/{interactive-mode-I3ILWXQB.js → interactive-mode-UFBSCOP7.js} +1 -1
  38. package/dist/bundle/chunks/kimi-coding.js +2 -2
  39. package/dist/bundle/chunks/{list-tips-2KX65NWZ.js → list-tips-2QG7ASHS.js} +1 -1
  40. package/dist/bundle/chunks/{mistral-conversations-VOBYATVJ.js → mistral-conversations-ZYDVFRYF.js} +1 -1
  41. package/dist/bundle/chunks/multi-session-host-EUTEGVLS.js +21 -0
  42. package/dist/bundle/chunks/openai-codex-responses-MEIUNROM.js +10 -0
  43. package/dist/bundle/chunks/{openai-completions-YK6QTYOH.js → openai-completions-4V76L5ZJ.js} +1 -1
  44. package/dist/bundle/chunks/openai-responses-YR7WRBG3.js +2 -0
  45. package/dist/bundle/chunks/{package-manager-cli-HOL6CAI6.js → package-manager-cli-JPUWMUEJ.js} +1 -1
  46. package/dist/bundle/chunks/rotation-stream-S56Y7WMM.js +2 -0
  47. package/dist/bundle/chunks/{rpc-mode-L3DSGTVM.js → rpc-mode-JDVHUBZY.js} +1 -1
  48. package/dist/bundle/chunks/{session-picker-L3FYEQBL.js → session-picker-PFPJVVIC.js} +1 -1
  49. package/dist/bundle/chunks/session-worker.js +225 -164
  50. package/dist/bundle/cli.js +1 -1
  51. package/dist/bundle/index.js +1 -1
  52. package/dist/bundle/rpc-entry.js +1 -1
  53. package/dist/core/agent-session.d.ts +6 -4
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +9 -7
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  58. package/dist/core/credential-pool/classify.js +12 -0
  59. package/dist/core/credential-pool/classify.js.map +1 -1
  60. package/dist/core/credential-pool/failover.d.ts +37 -5
  61. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  62. package/dist/core/credential-pool/failover.js +60 -24
  63. package/dist/core/credential-pool/failover.js.map +1 -1
  64. package/dist/core/credential-pool/rotation-events.d.ts +5 -0
  65. package/dist/core/credential-pool/rotation-events.d.ts.map +1 -0
  66. package/dist/core/credential-pool/rotation-events.js +25 -0
  67. package/dist/core/credential-pool/rotation-events.js.map +1 -0
  68. package/dist/core/credential-pool/rotation-stream.d.ts +4 -3
  69. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  70. package/dist/core/credential-pool/rotation-stream.js +8 -11
  71. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  72. package/dist/core/extensions/builtin/ask-user/schema.d.ts +2 -25
  73. package/dist/core/extensions/builtin/ask-user/schema.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/ask-user/schema.js.map +1 -1
  75. package/dist/core/extensions/builtin/claude-sdk-oauth/api-id.d.ts +7 -0
  76. package/dist/core/extensions/builtin/claude-sdk-oauth/api-id.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/claude-sdk-oauth/api-id.js +7 -0
  78. package/dist/core/extensions/builtin/claude-sdk-oauth/api-id.js.map +1 -0
  79. package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts.map +1 -1
  80. package/dist/core/extensions/builtin/claude-sdk-oauth/index.js +3 -2
  81. package/dist/core/extensions/builtin/claude-sdk-oauth/index.js.map +1 -1
  82. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +22 -1
  83. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  84. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.d.ts +2 -0
  85. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.js +4 -0
  87. package/dist/core/extensions/builtin/claude-sdk-oauth/session-observability.js.map +1 -1
  88. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts.map +1 -1
  89. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js +18 -1
  90. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js.map +1 -1
  91. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
  92. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +18 -0
  93. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
  94. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +2 -7
  95. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  96. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +2 -8
  97. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  98. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js +1 -1
  99. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js.map +1 -1
  100. package/dist/core/extensions/builtin/mcp/expose/naming.d.ts +0 -1
  101. package/dist/core/extensions/builtin/mcp/expose/naming.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/mcp/expose/naming.js +0 -3
  103. package/dist/core/extensions/builtin/mcp/expose/naming.js.map +1 -1
  104. package/dist/core/extensions/builtin/mcp/expose/register.d.ts +1 -2
  105. package/dist/core/extensions/builtin/mcp/expose/register.d.ts.map +1 -1
  106. package/dist/core/extensions/builtin/mcp/expose/register.js +0 -21
  107. package/dist/core/extensions/builtin/mcp/expose/register.js.map +1 -1
  108. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  109. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts.map +1 -1
  110. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +2 -2
  111. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js.map +1 -1
  112. package/dist/core/extensions/builtin/prompt-preset/file-operations.js +1 -1
  113. package/dist/core/extensions/builtin/prompt-preset/file-operations.js.map +1 -1
  114. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.d.ts +3 -0
  115. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.d.ts.map +1 -0
  116. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +105 -0
  117. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js.map +1 -0
  118. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  119. package/dist/core/extensions/builtin/prompt-preset/presets.js +14 -0
  120. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  121. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
  122. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
  123. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
  124. package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
  125. package/dist/core/extensions/bun-extension-importer.d.ts +1 -0
  126. package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
  127. package/dist/core/extensions/bun-extension-importer.js +4 -0
  128. package/dist/core/extensions/bun-extension-importer.js.map +1 -1
  129. package/dist/core/extensions/extension-module-cache.d.ts +48 -0
  130. package/dist/core/extensions/extension-module-cache.d.ts.map +1 -0
  131. package/dist/core/extensions/extension-module-cache.js +111 -0
  132. package/dist/core/extensions/extension-module-cache.js.map +1 -0
  133. package/dist/core/extensions/loader.d.ts +1 -1
  134. package/dist/core/extensions/loader.d.ts.map +1 -1
  135. package/dist/core/extensions/loader.js +14 -56
  136. package/dist/core/extensions/loader.js.map +1 -1
  137. package/dist/core/http-dispatcher.d.ts +29 -0
  138. package/dist/core/http-dispatcher.d.ts.map +1 -1
  139. package/dist/core/http-dispatcher.js +30 -3
  140. package/dist/core/http-dispatcher.js.map +1 -1
  141. package/dist/core/model-resolver.js +1 -1
  142. package/dist/core/model-resolver.js.map +1 -1
  143. package/dist/core/retry-fallback/billing.d.ts.map +1 -1
  144. package/dist/core/retry-fallback/billing.js +20 -1
  145. package/dist/core/retry-fallback/billing.js.map +1 -1
  146. package/dist/core/session-manager.d.ts +3 -1
  147. package/dist/core/session-manager.d.ts.map +1 -1
  148. package/dist/core/session-manager.js +12 -6
  149. package/dist/core/session-manager.js.map +1 -1
  150. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  151. package/dist/modes/interactive/components/assistant-render-descriptors.js +8 -7
  152. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  153. package/dist/modes/print-mode.d.ts.map +1 -1
  154. package/dist/modes/print-mode.js +7 -5
  155. package/dist/modes/print-mode.js.map +1 -1
  156. package/dist/modes/rpc/custom-capability.d.ts +7 -0
  157. package/dist/modes/rpc/custom-capability.d.ts.map +1 -1
  158. package/dist/modes/rpc/custom-capability.js +7 -0
  159. package/dist/modes/rpc/custom-capability.js.map +1 -1
  160. package/dist/modes/rpc/handoff-activity.d.ts +4 -0
  161. package/dist/modes/rpc/handoff-activity.d.ts.map +1 -0
  162. package/dist/modes/rpc/handoff-activity.js +6 -0
  163. package/dist/modes/rpc/handoff-activity.js.map +1 -0
  164. package/dist/modes/rpc/host-child-exit.d.ts +19 -0
  165. package/dist/modes/rpc/host-child-exit.d.ts.map +1 -0
  166. package/dist/modes/rpc/host-child-exit.js +35 -0
  167. package/dist/modes/rpc/host-child-exit.js.map +1 -0
  168. package/dist/modes/rpc/host-crash-record.d.ts +24 -0
  169. package/dist/modes/rpc/host-crash-record.d.ts.map +1 -0
  170. package/dist/modes/rpc/host-crash-record.js +75 -0
  171. package/dist/modes/rpc/host-crash-record.js.map +1 -0
  172. package/dist/modes/rpc/host-crash-record.test.d.ts +2 -0
  173. package/dist/modes/rpc/host-crash-record.test.d.ts.map +1 -0
  174. package/dist/modes/rpc/host-crash-record.test.js +91 -0
  175. package/dist/modes/rpc/host-crash-record.test.js.map +1 -0
  176. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  177. package/dist/modes/rpc/host-ensure.js +60 -17
  178. package/dist/modes/rpc/host-ensure.js.map +1 -1
  179. package/dist/modes/rpc/host-lifecycle.d.ts +4 -10
  180. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  181. package/dist/modes/rpc/host-lifecycle.js +83 -51
  182. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  183. package/dist/modes/rpc/host-supersession.d.ts +13 -2
  184. package/dist/modes/rpc/host-supersession.d.ts.map +1 -1
  185. package/dist/modes/rpc/host-supersession.js +40 -7
  186. package/dist/modes/rpc/host-supersession.js.map +1 -1
  187. package/dist/modes/rpc/multi-session-host.d.ts +1 -0
  188. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  189. package/dist/modes/rpc/multi-session-host.js +40 -7
  190. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  191. package/dist/modes/rpc/observer-link.d.ts +48 -0
  192. package/dist/modes/rpc/observer-link.d.ts.map +1 -0
  193. package/dist/modes/rpc/observer-link.js +56 -0
  194. package/dist/modes/rpc/observer-link.js.map +1 -0
  195. package/dist/modes/rpc/rpc-mode.d.ts +12 -2
  196. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  197. package/dist/modes/rpc/rpc-mode.js +12 -2
  198. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  199. package/dist/modes/rpc/rpc-types.d.ts +36 -1
  200. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  201. package/dist/modes/rpc/rpc-types.js +8 -0
  202. package/dist/modes/rpc/rpc-types.js.map +1 -1
  203. package/dist/modes/rpc/session-command-router.d.ts +10 -4
  204. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  205. package/dist/modes/rpc/session-command-router.js +95 -19
  206. package/dist/modes/rpc/session-command-router.js.map +1 -1
  207. package/dist/modes/rpc/session-event-writer.d.ts +5 -3
  208. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  209. package/dist/modes/rpc/session-event-writer.js +21 -2
  210. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  211. package/dist/modes/rpc/session-registry.d.ts +6 -1
  212. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  213. package/dist/modes/rpc/session-registry.js +34 -3
  214. package/dist/modes/rpc/session-registry.js.map +1 -1
  215. package/dist/modes/rpc/session-worker-client.d.ts +1 -0
  216. package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
  217. package/dist/modes/rpc/session-worker-client.js +3 -0
  218. package/dist/modes/rpc/session-worker-client.js.map +1 -1
  219. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -1
  220. package/dist/modes/rpc/session-worker-protocol.d.ts.map +1 -1
  221. package/dist/modes/rpc/session-worker-protocol.js.map +1 -1
  222. package/dist/modes/rpc/session-worker.js +7 -1
  223. package/dist/modes/rpc/session-worker.js.map +1 -1
  224. package/dist/modes/rpc/socket-ownership.d.ts +16 -0
  225. package/dist/modes/rpc/socket-ownership.d.ts.map +1 -1
  226. package/dist/modes/rpc/socket-ownership.js +28 -4
  227. package/dist/modes/rpc/socket-ownership.js.map +1 -1
  228. package/dist/modes/rpc/worker-session-registry.d.ts +12 -5
  229. package/dist/modes/rpc/worker-session-registry.d.ts.map +1 -1
  230. package/dist/modes/rpc/worker-session-registry.js +10 -2
  231. package/dist/modes/rpc/worker-session-registry.js.map +1 -1
  232. package/docs/providers.md +9 -1
  233. package/docs/rpc.md +72 -10
  234. package/docs/settings.md +1 -1
  235. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +29 -0
  236. package/node_modules/@code-yeongyu/senpi-codemode/README.md +51 -13
  237. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +53 -9
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/reserved-dispatch.ts +1 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/config/settings.ts +18 -0
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/eval-status.ts +9 -4
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +6 -0
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +2 -2
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +10 -0
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +18 -6
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +2 -6
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +1 -1
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +17 -7
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +6 -1
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +2 -5
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +37 -5
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +11 -2
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +6 -0
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +2 -5
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +14 -4
  256. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +18 -5
  257. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +3 -2
  258. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -1
  259. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/idle-timeout.ts +9 -2
  260. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/AGENTS.md +2 -0
  261. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-execution.ts +19 -2
  262. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-runtime.ts +11 -3
  263. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-contract.ts +6 -1
  264. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +72 -20
  265. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +28 -4
  266. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-state.ts +5 -4
  267. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-status.ts +5 -2
  268. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +76 -25
  269. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-execution-event.ts +4 -0
  270. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-kernel-reset-refused-error.ts +12 -0
  271. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-request.ts +2 -1
  272. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +3 -1
  273. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +58 -37
  274. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/managed-cell.ts +15 -8
  275. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +32 -8
  276. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +89 -25
  277. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/terminal-snapshot-store.ts +4 -0
  278. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +51 -10
  279. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +2 -1
  280. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  281. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +2 -13
  282. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  283. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +1 -0
  284. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  285. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +1 -0
  286. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  287. package/node_modules/@earendil-works/pi-agent-core/dist/tool-arguments.d.ts +5 -0
  288. package/node_modules/@earendil-works/pi-agent-core/dist/tool-arguments.d.ts.map +1 -0
  289. package/node_modules/@earendil-works/pi-agent-core/dist/tool-arguments.js +20 -0
  290. package/node_modules/@earendil-works/pi-agent-core/dist/tool-arguments.js.map +1 -0
  291. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  292. package/node_modules/@earendil-works/pi-ai/README.md +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +14 -56
  294. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  296. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +18 -27
  297. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  298. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.d.ts +40 -0
  299. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.d.ts.map +1 -0
  300. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.js +102 -0
  301. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.js.map +1 -0
  302. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts +4 -3
  303. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +28 -14
  305. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js.map +1 -1
  306. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.d.ts +44 -0
  307. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.d.ts.map +1 -0
  308. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.js +120 -0
  309. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.js.map +1 -0
  310. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +2 -2
  311. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +2 -2
  313. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +3 -0
  315. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
  316. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +21 -15
  317. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts +1 -0
  319. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/compat.js +1 -0
  321. package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
  322. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +2 -0
  323. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  324. package/node_modules/@earendil-works/pi-ai/dist/index.js +2 -0
  325. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  326. package/node_modules/@earendil-works/pi-ai/dist/legacy-provider-ids.d.ts +8 -0
  327. package/node_modules/@earendil-works/pi-ai/dist/legacy-provider-ids.d.ts.map +1 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/legacy-provider-ids.js +26 -0
  329. package/node_modules/@earendil-works/pi-ai/dist/legacy-provider-ids.js.map +1 -0
  330. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  331. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  332. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  333. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  334. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  335. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  336. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  337. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  338. package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.json +1 -1
  339. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  340. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
  341. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  342. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  343. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  344. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
  345. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.d.ts +9 -0
  346. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.d.ts.map +1 -0
  347. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.js +43 -0
  348. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.js.map +1 -0
  349. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.d.ts.map +1 -1
  350. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +3 -2
  351. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js.map +1 -1
  352. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.d.ts +19 -0
  353. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.d.ts.map +1 -0
  354. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.js +63 -0
  355. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.js.map +1 -0
  356. package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/classifiers.d.ts.map +1 -1
  357. package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/classifiers.js +5 -1
  358. package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/classifiers.js.map +1 -1
  359. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +17 -0
  360. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  361. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +22 -1
  362. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  363. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  364. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  365. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  366. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  367. package/node_modules/@earendil-works/pi-tui/dist/tui.js +12 -2
  368. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  369. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  370. package/package.json +7 -7
  371. package/dist/bundle/chunks/chunk-5J5R5AMP.js +0 -8
  372. package/dist/bundle/chunks/chunk-I3F6VZZF.js +0 -2
  373. package/dist/bundle/chunks/chunk-IDTLJYJ5.js +0 -10
  374. package/dist/bundle/chunks/chunk-U6Z6OBX4.js +0 -2
  375. package/dist/bundle/chunks/host-lifecycle-DGZJTBDJ.js +0 -3
  376. package/dist/bundle/chunks/multi-session-host-S5FWCIWE.js +0 -20
  377. package/dist/bundle/chunks/openai-codex-responses-L2PO2D6F.js +0 -10
  378. package/dist/bundle/chunks/openai-responses-YDUBVF4S.js +0 -2
  379. package/dist/bundle/chunks/rotation-stream-Y4YU6HV4.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"socket-ownership.js","sourceRoot":"","sources":["../../../src/modes/rpc/socket-ownership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ownership-checked removal for Unix domain socket path entries.\n *\n * A socket path is a rendezvous name, not a capability: once a host unlinks\n * its entry, any other process may bind the same path. Teardown that removes\n * \"its\" socket by path alone can therefore delete a NEWER host's freshly\n * published entry after a takeover renamed a replacement over that path. The\n * startup path already refuses to touch a socket owned by a live server\n * (`prepareSocketPath`); these helpers give every teardown path the same rule:\n * capture the bound entry's filesystem identity (dev + ino) at listen time and\n * unlink only while the path still stat-matches that identity.\n */\nimport { mkdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\n/** Filesystem identity of one bound socket path entry. */\nexport interface SocketFileIdentity {\n\treadonly dev: number;\n\treadonly ino: number;\n}\n\n/**\n * Name of the sidecar, inside the lifecycle supervisor's private scratch\n * directory, that records which public socket entry that supervisor bound. The\n * directory is per-supervisor, so a replacement supervisor's token can never be\n * mistaken for the old one's.\n */\nexport const PUBLIC_SOCKET_IDENTITY_FILE = \"public-socket.owner\";\n\n/** Default bound on waiting for a supervisor to publish its ownership token. */\nexport const SOCKET_IDENTITY_WAIT_MS = 30_000;\n\n/**\n * `sun_path` is 104 bytes INCLUDING its terminator on macOS, so a bindable path fits in 103.\n * A generation's bind path is the longest name this daemon ever produces, which makes it the\n * one that must be checked before a bind turns the overflow into a truncated, silently wrong\n * endpoint.\n */\nexport const MAX_SOCKET_PATH_BYTES = 103;\n\n/**\n * Where a successor generation binds before it owns the public name. It never binds the live\n * public path: that path belongs to the running host until the successor is proven to work.\n */\nexport function generationBindPath(publicSocket: string, generation: number): string {\n\treturn `${publicSocket}.next-${generation}`;\n}\n\nfunction sameSocketIdentity(a: SocketFileIdentity, b: SocketFileIdentity): boolean {\n\treturn a.dev === b.dev && a.ino === b.ino;\n}\n\n/** Reads the current identity of a socket path entry; `undefined` once absent. */\nexport async function statSocketIdentity(socketPath: string): Promise<SocketFileIdentity | undefined> {\n\ttry {\n\t\tconst { dev, ino } = await stat(socketPath);\n\t\treturn { dev, ino };\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n}\n\n/**\n * Whether `socketPath` is served by a DIFFERENT entry than `identity` describes: a successor\n * generation renamed its own socket over it, or something else took the name. A generation that\n * finds this true no longer owns the endpoint and cannot be reached by path any more.\n *\n * An absent entry and an unknown identity both answer `false`. Supersession has to be POSITIVELY\n * observed - a name that is merely missing is somebody's `rm`, not a newer host, and treating it as\n * one would make a healthy daemon drain itself.\n */\nexport async function socketEntryReplaced(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n): Promise<boolean> {\n\tif (identity === undefined || process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return false;\n\tconst current = await statSocketIdentity(socketPath).catch(() => undefined);\n\treturn current !== undefined && !sameSocketIdentity(current, identity);\n}\n\n/** Records a bound socket identity for teardown paths that cannot hold it in memory. */\nexport async function writeSocketIdentityFile(path: string, identity: SocketFileIdentity): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true, mode: 0o700 });\n\tconst tmpPath = `${path}.${process.pid}.tmp`;\n\tawait writeFile(tmpPath, `${JSON.stringify(identity)}\\n`, { mode: 0o600 });\n\tawait rename(tmpPath, path);\n}\n\nexport async function readSocketIdentityFile(path: string): Promise<SocketFileIdentity | undefined> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\t// Truncated or in-progress JSON is not a token: callers wait or leave the path.\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\ttypeof (parsed as { dev?: unknown }).dev !== \"number\" ||\n\t\ttypeof (parsed as { ino?: unknown }).ino !== \"number\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { dev: (parsed as { dev: number }).dev, ino: (parsed as { ino: number }).ino };\n}\n\n/**\n * Waits, bounded, for an identity file to appear. The lifecycle supervisor\n * publishes its public-socket token after this host's internal listener is\n * ready, so supervised hosts poll briefly instead of assuming ordering.\n */\nexport async function waitForSocketIdentityFile(\n\tpath: string,\n\ttimeoutMs: number = SOCKET_IDENTITY_WAIT_MS,\n\tintervalMs = 25,\n): Promise<SocketFileIdentity | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\tfor (;;) {\n\t\tconst identity = await readSocketIdentityFile(path);\n\t\tif (identity !== undefined) return identity;\n\t\tif (Date.now() >= deadline) return undefined;\n\t\tawait delayUnrefed(intervalMs);\n\t}\n}\n\n/**\n * Unlinks a socket path only while it still refers to the entry `identity`\n * describes. An absent path is nothing to do; a mismatched identity means a\n * newer host owns the name now and is left alone; an unknown identity can\n * never be proven ours, so the path is left and the decision is logged.\n */\nexport async function unlinkOwnedSocket(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n\tlog: (message: string) => void,\n): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return;\n\tlet current: SocketFileIdentity | undefined;\n\ttry {\n\t\tcurrent = await statSocketIdentity(socketPath);\n\t} catch (cause) {\n\t\tlog(`socket ${socketPath} ownership could not be verified (${errorMessage(cause)}); leaving it`);\n\t\treturn;\n\t}\n\tif (current === undefined) return;\n\tif (identity === undefined) {\n\t\tlog(`socket path ${socketPath} ownership unknown; leaving it`);\n\t\treturn;\n\t}\n\tif (!sameSocketIdentity(current, identity)) {\n\t\tlog(`socket path ${socketPath} now owned by another host; leaving it`);\n\t\treturn;\n\t}\n\ttry {\n\t\tawait unlink(socketPath);\n\t} catch (cause) {\n\t\tif (!isNodeErrorCode(cause, \"ENOENT\")) {\n\t\t\tlog(`socket ${socketPath} removal failed (${errorMessage(cause)})`);\n\t\t}\n\t}\n}\n\n/**\n * Runs `close` with the socket path's current entry shielded from libuv's\n * close-time unlink.\n *\n * libuv unlinks the NAME a pipe was bound to when its listening handle\n * closes - even when a takeover has renamed a DIFFERENT socket over that\n * name, so an ordinary `server.close()` silently deletes the newer host's\n * freshly published entry. Moving the current entry aside for the duration\n * of the close (libuv's unlink then hits ENOENT) and restoring it afterwards\n * keeps the filesystem exactly as it was; ownership-checked removal via\n * `unlinkOwnedSocket` decides afterwards what may actually be deleted.\n */\nexport async function shieldSocketDuringClose(socketPath: string, close: () => Promise<void>): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return close();\n\tconst shieldPath = `${socketPath}.shield-${process.pid}`;\n\tlet shielded = false;\n\ttry {\n\t\tif ((await statSocketIdentity(socketPath)) !== undefined) {\n\t\t\tawait rename(socketPath, shieldPath);\n\t\t\tshielded = true;\n\t\t}\n\t} catch {\n\t\t// Without a shield the close degrades to today's behavior; the\n\t\t// ownership-checked unlink still runs afterwards.\n\t}\n\ttry {\n\t\tawait close();\n\t} finally {\n\t\tif (shielded) {\n\t\t\ttry {\n\t\t\t\tawait rename(shieldPath, socketPath);\n\t\t\t} catch {\n\t\t\t\t// Best effort: a failed restore leaves the entry under the shield\n\t\t\t\t// name; startup probes treat a dead entry as replaceable.\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction delayUnrefed(ms: number): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, ms);\n\t\ttimer.unref?.();\n\t});\n}\n\nfunction errorMessage(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\n}\n\nfunction isNodeErrorCode(cause: unknown, code: string): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === code;\n}\n"]}
1
+ {"version":3,"file":"socket-ownership.js","sourceRoot":"","sources":["../../../src/modes/rpc/socket-ownership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ownership-checked removal for Unix domain socket path entries.\n *\n * A socket path is a rendezvous name, not a capability: once a host unlinks\n * its entry, any other process may bind the same path. Teardown that removes\n * \"its\" socket by path alone can therefore delete a NEWER host's freshly\n * published entry after a takeover renamed a replacement over that path. The\n * startup path already refuses to touch a socket owned by a live server\n * (`prepareSocketPath`); these helpers give every teardown path the same rule:\n * capture the bound entry's filesystem identity (dev + ino) at listen time and\n * unlink only while the path still stat-matches that identity.\n */\nimport { mkdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\n/** Filesystem identity of one bound socket path entry. */\nexport interface SocketFileIdentity {\n\treadonly dev: number;\n\treadonly ino: number;\n}\n\n/**\n * Name of the sidecar, inside the lifecycle supervisor's private scratch\n * directory, that records which public socket entry that supervisor bound. The\n * directory is per-supervisor, so a replacement supervisor's token can never be\n * mistaken for the old one's.\n */\nexport const PUBLIC_SOCKET_IDENTITY_FILE = \"public-socket.owner\";\n\n/** What the public path holds relative to the entry a generation bound there. */\nexport type EndpointOwnership = \"held\" | \"replaced\" | \"absent\" | \"unknown\";\n\n/** Default bound on waiting for a supervisor to publish its ownership token. */\nexport const SOCKET_IDENTITY_WAIT_MS = 30_000;\n\n/**\n * `sun_path` is 104 bytes INCLUDING its terminator on macOS, so a bindable path fits in 103.\n * A generation's bind path is the longest name this daemon ever produces, which makes it the\n * one that must be checked before a bind turns the overflow into a truncated, silently wrong\n * endpoint.\n */\nexport const MAX_SOCKET_PATH_BYTES = 103;\n\n/**\n * Where a successor generation binds before it owns the public name. It never binds the live\n * public path: that path belongs to the running host until the successor is proven to work.\n */\nexport function generationBindPath(publicSocket: string, generation: number): string {\n\treturn `${publicSocket}.next-${generation}`;\n}\n\nfunction sameSocketIdentity(a: SocketFileIdentity, b: SocketFileIdentity): boolean {\n\treturn a.dev === b.dev && a.ino === b.ino;\n}\n\n/** Reads the current identity of a socket path entry; `undefined` once absent. */\nexport async function statSocketIdentity(socketPath: string): Promise<SocketFileIdentity | undefined> {\n\ttry {\n\t\tconst { dev, ino } = await stat(socketPath);\n\t\treturn { dev, ino };\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n}\n\n/**\n * What the public path now holds relative to the entry `identity` describes, in ONE stat so the\n * answers cannot disagree with each other:\n *\n * - `held`: still this generation's own entry.\n * - `replaced`: a successor renamed its socket over it, or something else took the name.\n * - `absent`: no entry at all - somebody's `rm`, a swept temp directory, a deleted workspace.\n * - `unknown`: nothing can be proven (no identity, win32, an abstract socket, a failed stat).\n *\n * `replaced` and `absent` are both losses of reachability, but they are NOT the same observation\n * and callers must keep them apart: a successor is serving clients on that name, while an absent\n * name serves nobody. `unknown` is never a claim.\n */\nexport async function classifyEndpointOwnership(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n): Promise<EndpointOwnership> {\n\tif (identity === undefined || process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return \"unknown\";\n\tlet current: SocketFileIdentity | undefined;\n\ttry {\n\t\tcurrent = await statSocketIdentity(socketPath);\n\t} catch {\n\t\treturn \"unknown\";\n\t}\n\tif (current === undefined) return \"absent\";\n\treturn sameSocketIdentity(current, identity) ? \"held\" : \"replaced\";\n}\n\n/**\n * Whether `socketPath` is served by a DIFFERENT entry than `identity` describes: a successor\n * generation renamed its own socket over it, or something else took the name. A generation that\n * finds this true no longer owns the endpoint and cannot be reached by path any more.\n *\n * An absent entry and an unknown identity both answer `false`. Supersession has to be POSITIVELY\n * observed - a name that is merely missing is somebody's `rm`, not a newer host, and treating it as\n * one would make a healthy daemon drain itself.\n */\nexport async function socketEntryReplaced(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n): Promise<boolean> {\n\treturn (await classifyEndpointOwnership(socketPath, identity)) === \"replaced\";\n}\n\n/** Records a bound socket identity for teardown paths that cannot hold it in memory. */\nexport async function writeSocketIdentityFile(path: string, identity: SocketFileIdentity): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true, mode: 0o700 });\n\tconst tmpPath = `${path}.${process.pid}.tmp`;\n\tawait writeFile(tmpPath, `${JSON.stringify(identity)}\\n`, { mode: 0o600 });\n\tawait rename(tmpPath, path);\n}\n\nexport async function readSocketIdentityFile(path: string): Promise<SocketFileIdentity | undefined> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\t// Truncated or in-progress JSON is not a token: callers wait or leave the path.\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\ttypeof (parsed as { dev?: unknown }).dev !== \"number\" ||\n\t\ttypeof (parsed as { ino?: unknown }).ino !== \"number\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { dev: (parsed as { dev: number }).dev, ino: (parsed as { ino: number }).ino };\n}\n\n/**\n * Waits, bounded, for an identity file to appear. The lifecycle supervisor\n * publishes its public-socket token after this host's internal listener is\n * ready, so supervised hosts poll briefly instead of assuming ordering.\n */\nexport async function waitForSocketIdentityFile(\n\tpath: string,\n\ttimeoutMs: number = SOCKET_IDENTITY_WAIT_MS,\n\tintervalMs = 25,\n): Promise<SocketFileIdentity | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\tfor (;;) {\n\t\tconst identity = await readSocketIdentityFile(path);\n\t\tif (identity !== undefined) return identity;\n\t\tif (Date.now() >= deadline) return undefined;\n\t\tawait delayUnrefed(intervalMs);\n\t}\n}\n\n/**\n * Unlinks a socket path only while it still refers to the entry `identity`\n * describes. An absent path is nothing to do; a mismatched identity means a\n * newer host owns the name now and is left alone; an unknown identity can\n * never be proven ours, so the path is left and the decision is logged.\n */\nexport async function unlinkOwnedSocket(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n\tlog: (message: string) => void,\n): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return;\n\tlet current: SocketFileIdentity | undefined;\n\ttry {\n\t\tcurrent = await statSocketIdentity(socketPath);\n\t} catch (cause) {\n\t\tlog(`socket ${socketPath} ownership could not be verified (${errorMessage(cause)}); leaving it`);\n\t\treturn;\n\t}\n\tif (current === undefined) return;\n\tif (identity === undefined) {\n\t\tlog(`socket path ${socketPath} ownership unknown; leaving it`);\n\t\treturn;\n\t}\n\tif (!sameSocketIdentity(current, identity)) {\n\t\tlog(`socket path ${socketPath} now owned by another host; leaving it`);\n\t\treturn;\n\t}\n\ttry {\n\t\tawait unlink(socketPath);\n\t} catch (cause) {\n\t\tif (!isNodeErrorCode(cause, \"ENOENT\")) {\n\t\t\tlog(`socket ${socketPath} removal failed (${errorMessage(cause)})`);\n\t\t}\n\t}\n}\n\n/**\n * Runs `close` with the socket path's current entry shielded from libuv's\n * close-time unlink.\n *\n * libuv unlinks the NAME a pipe was bound to when its listening handle\n * closes - even when a takeover has renamed a DIFFERENT socket over that\n * name, so an ordinary `server.close()` silently deletes the newer host's\n * freshly published entry. Moving the current entry aside for the duration\n * of the close (libuv's unlink then hits ENOENT) and restoring it afterwards\n * keeps the filesystem exactly as it was; ownership-checked removal via\n * `unlinkOwnedSocket` decides afterwards what may actually be deleted.\n */\nexport async function shieldSocketDuringClose(socketPath: string, close: () => Promise<void>): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return close();\n\tconst shieldPath = `${socketPath}.shield-${process.pid}`;\n\tlet shielded = false;\n\ttry {\n\t\tif ((await statSocketIdentity(socketPath)) !== undefined) {\n\t\t\tawait rename(socketPath, shieldPath);\n\t\t\tshielded = true;\n\t\t}\n\t} catch {\n\t\t// Without a shield the close degrades to today's behavior; the\n\t\t// ownership-checked unlink still runs afterwards.\n\t}\n\ttry {\n\t\tawait close();\n\t} finally {\n\t\tif (shielded) {\n\t\t\ttry {\n\t\t\t\tawait rename(shieldPath, socketPath);\n\t\t\t} catch {\n\t\t\t\t// Best effort: a failed restore leaves the entry under the shield\n\t\t\t\t// name; startup probes treat a dead entry as replaceable.\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction delayUnrefed(ms: number): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, ms);\n\t\ttimer.unref?.();\n\t});\n}\n\nfunction errorMessage(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\n}\n\nfunction isNodeErrorCode(cause: unknown, code: string): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === code;\n}\n"]}
@@ -1,5 +1,14 @@
1
1
  import type { CliRuntimeConfiguration } from "../../main.ts";
2
2
  import { type OpenRpcSession, type RpcSessionEntry, type RpcSessionLaunchProfile, type RpcSessionOpenOptions, type RpcSessionRow } from "./session-registry.ts";
3
+ import { SessionWorkerClient } from "./session-worker-client.ts";
4
+ type SessionWorkerCallbacks = ConstructorParameters<typeof SessionWorkerClient>[0];
5
+ export interface WorkerSessionRegistryOptions {
6
+ readonly configuration: CliRuntimeConfiguration;
7
+ readonly closeGraceMs: number;
8
+ readonly now: () => number;
9
+ /** Production builds the real worker; a caller may supply one to drive a lifecycle path deterministically. */
10
+ readonly createWorker?: (callbacks: SessionWorkerCallbacks) => SessionWorkerClient;
11
+ }
3
12
  /** Transport-side lifecycle owner. Caller paths are never inspected on this event loop. */
4
13
  export declare class WorkerSessionRegistry {
5
14
  private readonly entries;
@@ -8,11 +17,8 @@ export declare class WorkerSessionRegistry {
8
17
  readonly closeGraceMs: number;
9
18
  private readonly now;
10
19
  private readonly options;
11
- constructor(options: {
12
- configuration: CliRuntimeConfiguration;
13
- closeGraceMs: number;
14
- now: () => number;
15
- });
20
+ private readonly createWorker;
21
+ constructor(options: WorkerSessionRegistryOptions);
16
22
  get size(): number;
17
23
  /**
18
24
  * Memory admission is the in-process registry's concern: a worker-runtime session runs in
@@ -46,4 +52,5 @@ export declare class WorkerSessionRegistry {
46
52
  private liveWorkerPaths;
47
53
  private openResult;
48
54
  }
55
+ export {};
49
56
  //# sourceMappingURL=worker-session-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-session-registry.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/worker-session-registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAM7D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAE1B,KAAK,aAAa,EAElB,MAAM,uBAAuB,CAAC;AAI/B,2FAA2F;AAC3F,qBAAa,qBAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAC9D,OAAO,CAAC,MAAM,CAAK;IACnB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsF;IAE9G,YAAY,OAAO,EAAE;QAAE,aAAa,EAAE,uBAAuB,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,MAAM,CAAA;KAAE,EAIvG;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,kBAAkB,IAAI,IAAI,CAAG;IAEvB,WAAW,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgE5G;IAED,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhD;IAED,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CAY9D;IAED,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,eAAe,CAsBjH;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnC;IAEK,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/C;IAED,IAAI,IAAI,aAAa,EAAE,CAgBtB;IAED,mGAAmG;IACnG,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,MAAM;IAad,wFAAwF;IACxF,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvC;IAED,OAAO,CAAC,OAAO;IAQf;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAUjB,mGAAmG;IACnG,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,UAAU;CAKlB"}
1
+ {"version":3,"file":"worker-session-registry.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/worker-session-registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAM7D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAE1B,KAAK,aAAa,EAElB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,KAAK,sBAAsB,GAAG,qBAAqB,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,8GAA8G;IAC9G,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,sBAAsB,KAAK,mBAAmB,CAAC;CACnF;AAED,2FAA2F;AAC3F,qBAAa,qBAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAC9D,OAAO,CAAC,MAAM,CAAK;IACnB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6D;IAE1F,YAAY,OAAO,EAAE,4BAA4B,EAKhD;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,kBAAkB,IAAI,IAAI,CAAG;IAEvB,WAAW,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CAwE5G;IAED,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhD;IAED,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CAY9D;IAED,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,eAAe,CAsBjH;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnC;IAEK,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/C;IAED,IAAI,IAAI,aAAa,EAAE,CAgBtB;IAED,mGAAmG;IACnG,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,MAAM;IAad,wFAAwF;IACxF,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvC;IAED,OAAO,CAAC,OAAO;IAQf;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAUjB,mGAAmG;IACnG,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,UAAU;CAKlB"}
@@ -13,6 +13,7 @@ export class WorkerSessionRegistry {
13
13
  this.options = options;
14
14
  this.closeGraceMs = options.closeGraceMs;
15
15
  this.now = options.now;
16
+ this.createWorker = options.createWorker ?? ((callbacks) => new SessionWorkerClient(callbacks));
16
17
  }
17
18
  get size() {
18
19
  return this.entries.size;
@@ -46,7 +47,8 @@ export class WorkerSessionRegistry {
46
47
  lastCommandAt: this.now(),
47
48
  lifecycleMutex: Promise.resolve(),
48
49
  };
49
- const worker = new SessionWorkerClient({
50
+ let workerFailure;
51
+ const worker = this.createWorker({
50
52
  reserve: (path) => this.reserve(handle, path),
51
53
  reconcile: (livePaths) => this.reconcile(handle, livePaths),
52
54
  exit: () => {
@@ -58,6 +60,10 @@ export class WorkerSessionRegistry {
58
60
  entry.closeResolve?.();
59
61
  },
60
62
  failure: (error) => {
63
+ // The open below only learns that its entry left `opening`, never why. Without this the
64
+ // caller is told `session_closing` - a path whose owner is tearing down - for a worker
65
+ // that died, and the actual reason reaches stderr alone (#1953).
66
+ workerFailure = error;
61
67
  entry.state = "quarantined";
62
68
  process.stderr.write(`senpi rpc session ${handle} quarantined: ${error}\n`);
63
69
  },
@@ -81,7 +87,9 @@ export class WorkerSessionRegistry {
81
87
  entry.sessionPath = path;
82
88
  const snapshot = await worker.commit();
83
89
  if (entry.state !== "opening")
84
- throw new RpcSessionRegistryError("session_closing");
90
+ throw workerFailure === undefined
91
+ ? new RpcSessionRegistryError("session_closing")
92
+ : new RpcSessionRegistryError("open_failed", workerFailure);
85
93
  entry.durableSessionId = snapshot.state.sessionId;
86
94
  entry.cwd = snapshot.state.cwd;
87
95
  entry.state = "open";
@@ -1 +1 @@
1
- {"version":3,"file":"worker-session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/worker-session-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { ProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport type { CliRuntimeConfiguration } from \"../../main.ts\";\nimport {\n\ttype LiveWorkerPaths,\n\tRESERVATION_DENIAL_CODES,\n\tSessionPathReservations,\n} from \"./session-path-reservations.ts\";\nimport {\n\tfrozenProfile,\n\ttype OpenRpcSession,\n\ttype RpcSessionEntry,\n\ttype RpcSessionLaunchProfile,\n\ttype RpcSessionOpenOptions,\n\tRpcSessionRegistryError,\n\ttype RpcSessionRow,\n\tsessionIdentity,\n} from \"./session-registry.ts\";\nimport { SessionWorkerClient } from \"./session-worker-client.ts\";\nimport { SESSION_WORKER_LIMITS, type SessionWriteGrant } from \"./session-worker-protocol.ts\";\n\n/** Transport-side lifecycle owner. Caller paths are never inspected on this event loop. */\nexport class WorkerSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new SessionPathReservations();\n\tprivate serial = 0;\n\treadonly closeGraceMs: number;\n\tprivate readonly now: () => number;\n\n\tprivate readonly options: { configuration: CliRuntimeConfiguration; closeGraceMs: number; now: () => number };\n\n\tconstructor(options: { configuration: CliRuntimeConfiguration; closeGraceMs: number; now: () => number }) {\n\t\tthis.options = options;\n\t\tthis.closeGraceMs = options.closeGraceMs;\n\t\tthis.now = options.now;\n\t}\n\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\t/**\n\t * Memory admission is the in-process registry's concern: a worker-runtime session runs in\n\t * its own isolate and this registry already bounds occupancy with `too_many_sessions`.\n\t */\n\tsetWorkerAdmission(): void {}\n\n\tasync openSession(profile: RpcSessionLaunchProfile, options?: RpcSessionOpenOptions): Promise<OpenRpcSession> {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath)))\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\tif (profile.sessionPath) {\n\t\t\tconst key = this.knownReservationKey(profile.sessionPath);\n\t\t\tconst owner = key ? this.reservations.owner(key) : undefined;\n\t\t\tif (key && owner) return this.attach(owner, key, options);\n\t\t}\n\t\tif (this.size >= SESSION_WORKER_LIMITS.workers) throw new Error(\"too_many_sessions\");\n\t\tconst handle = `rpc-${++this.serial}`;\n\t\tconst storedProfile = frozenProfile(profile);\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\t...sessionIdentity(storedProfile),\n\t\t\tcwd: profile.cwd,\n\t\t\tattachments: 1,\n\t\t\tretainOnDisconnect: options?.retainOnDisconnect === true,\n\t\t\tlastCommandAt: this.now(),\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tconst worker = new SessionWorkerClient({\n\t\t\treserve: (path) => this.reserve(handle, path),\n\t\t\treconcile: (livePaths) => this.reconcile(handle, livePaths),\n\t\t\texit: () => {\n\t\t\t\tif (this.entries.get(handle) !== entry) return;\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tthis.reservations.releaseAll(handle);\n\t\t\t\tentry.closeResolve?.();\n\t\t\t},\n\t\t\tfailure: (error) => {\n\t\t\t\tentry.state = \"quarantined\";\n\t\t\t\tprocess.stderr.write(`senpi rpc session ${handle} quarantined: ${error}\\n`);\n\t\t\t},\n\t\t});\n\t\tentry.worker = worker;\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst path = await worker.prepare(this.options.configuration, profile);\n\t\t\tconst owner = this.reservations.owner(path);\n\t\t\tif (owner) {\n\t\t\t\tconst attached = this.attach(owner, path, options);\n\t\t\t\tentry.state = \"quarantined\";\n\t\t\t\tworker.quarantine();\n\t\t\t\treturn attached;\n\t\t\t}\n\t\t\tconst grant = this.reserve(handle, path);\n\t\t\tif (grant !== \"granted\") throw new RpcSessionRegistryError(RESERVATION_DENIAL_CODES[grant]);\n\t\t\tentry.reservationKey = path;\n\t\t\tentry.requestedPathKey = profile.sessionPath ? path : undefined;\n\t\t\tentry.sessionPath = path;\n\t\t\tconst snapshot = await worker.commit();\n\t\t\tif (entry.state !== \"opening\") throw new RpcSessionRegistryError(\"session_closing\");\n\t\t\tentry.durableSessionId = snapshot.state.sessionId;\n\t\t\tentry.cwd = snapshot.state.cwd;\n\t\t\tentry.state = \"open\";\n\t\t\treturn this.openResult(handle, entry);\n\t\t} catch (cause) {\n\t\t\tentry.state = \"quarantined\";\n\t\t\tworker.quarantine();\n\t\t\tthrow cause;\n\t\t}\n\t}\n\n\tpeek(handle: string): RpcSessionEntry | undefined {\n\t\treturn this.entries.get(handle);\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (\n\t\t\tentry.state === \"quarantined\" ||\n\t\t\t(entry.state === \"closing\" &&\n\t\t\t\t![\"abort\", \"abort_bash\", \"extension_ui_response\", \"extension_ui_progress\"].includes(command))\n\t\t)\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.lastCommandAt = this.now();\n\t\treturn entry;\n\t}\n\n\tbeginClose(handle: string, onRole?: (finalizer: boolean) => void, options?: { detach?: boolean }): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" || entry.state === \"quarantined\") {\n\t\t\tonRole?.(false);\n\t\t\treturn entry;\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"opening\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.attachments--;\n\t\tif (entry.attachments > 0) return entry;\n\t\t// A retained session answers a client's detach by staying open at zero\n\t\t// attachments; only an explicit close or eviction reaches the worker.\n\t\tif (options?.detach && entry.retainOnDisconnect) {\n\t\t\tentry.attachments = 0;\n\t\t\treturn entry;\n\t\t}\n\t\tentry.state = \"closing\";\n\t\tentry.closeCompletion = new Promise((resolve) => {\n\t\t\tentry.closeResolve = resolve;\n\t\t});\n\t\tonRole?.(true);\n\t\treturn entry;\n\t}\n\n\tclose(handle: string): Promise<void> {\n\t\tconst entry = this.beginClose(handle);\n\t\treturn entry.state === \"closing\" ? this.closeMarked(handle) : Promise.resolve();\n\t}\n\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\" || !entry.worker) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.closeStarted) return entry.closeCompletion;\n\t\tentry.closeStarted = true;\n\t\t// Reply on a bounded deadline, but keep entry, attachments and reservations until exit.\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tawait Promise.race([\n\t\t\tentry.worker.close(this.closeGraceMs),\n\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (entry.state !== \"closed\") entry.state = \"quarantined\";\n\t\t\t\t\tresolve();\n\t\t\t\t}, this.closeGraceMs);\n\t\t\t}),\n\t\t]);\n\t\tif (timer) clearTimeout(timer);\n\t}\n\n\tlist(): RpcSessionRow[] {\n\t\treturn [...this.entries].map(([sessionId, entry]) => {\n\t\t\tconst state = entry.worker?.snapshot?.state;\n\t\t\treturn {\n\t\t\t\tsessionId,\n\t\t\t\tdurableSessionId: state?.sessionId ?? entry.durableSessionId,\n\t\t\t\tsessionPath: state?.sessionFile ?? entry.sessionPath,\n\t\t\t\tcwd: state?.cwd ?? entry.cwd,\n\t\t\t\tname: state?.sessionName,\n\t\t\t\tkind: entry.kind,\n\t\t\t\tcontext: entry.context,\n\t\t\t\t// A closing entry has already released its last attachment; never publish that as negative.\n\t\t\t\tattachments: Math.max(0, entry.attachments),\n\t\t\t\tstatus: entry.state === \"quarantined\" ? \"closing\" : entry.state,\n\t\t\t};\n\t\t});\n\t}\n\n\t/** Only compare spellings already tied to a granted identity; do not inspect caller paths here. */\n\tprivate knownReservationKey(path: string): string | undefined {\n\t\tif (this.reservations.owner(path)) return path;\n\t\tfor (const entry of this.entries.values()) {\n\t\t\t// An opening spelling maps only while the key granted for it is still held: a\n\t\t\t// superseded path belongs to nobody and must open its own worker.\n\t\t\tif (\n\t\t\t\tentry.profile.sessionPath === path &&\n\t\t\t\tentry.requestedPathKey &&\n\t\t\t\tthis.reservations.owner(entry.requestedPathKey)\n\t\t\t)\n\t\t\t\treturn entry.requestedPathKey;\n\t\t\tconst snapshot = entry.worker?.snapshot;\n\t\t\tif (snapshot?.state.sessionFile === path) return snapshot.sessionPath;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate attach(owner: string, path: string, options?: RpcSessionOpenOptions): OpenRpcSession {\n\t\tconst entry = this.entries.get(owner);\n\t\tif (entry?.state !== \"open\" || !entry.worker?.bindingReady || entry.worker.snapshot?.sessionPath !== path)\n\t\t\tthrow new RpcSessionRegistryError(\"session_path_in_use\");\n\t\tconst result = this.openResult(owner, entry);\n\t\tentry.attachments++;\n\t\t// Retention is a property of the live session: any attach may ask for it, and\n\t\t// no attach may revoke it for the clients that already rely on it.\n\t\tif (options?.retainOnDisconnect) entry.retainOnDisconnect = true;\n\t\tentry.lastCommandAt = this.now();\n\t\treturn { ...result, attached: true };\n\t}\n\n\t/** Granted paths currently held by a handle; the per-worker budget is bounded by it. */\n\treservationCount(handle: string): number {\n\t\treturn this.reservations.count(handle);\n\t}\n\n\tprivate reserve(handle: string, path: string): SessionWriteGrant {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) return \"conflict\";\n\t\tif (this.reservations.owner(path) === handle) return \"granted\";\n\t\tif (entry.state !== \"opening\" && entry.state !== \"open\") return \"conflict\";\n\t\treturn this.reservations.reserve(handle, path, this.liveWorkerPaths(entry));\n\t}\n\n\t/**\n\t * Releases the grants a worker's latest snapshot no longer claims.\n\t *\n\t * Only a fully open entry reports live writers. An entry still opening, closing or\n\t * quarantined keeps every grant until its real exit, because a worker stuck in a\n\t * syscall can still be writing a path it can no longer report.\n\t */\n\tprivate reconcile(handle: string, livePaths: readonly string[]): void {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") return;\n\t\tconst sessionPath = entry.worker?.snapshot?.sessionPath;\n\t\tthis.reservations.reconcile(handle, { livePaths, sessionPath });\n\t\tif (!sessionPath) return;\n\t\tentry.reservationKey = sessionPath;\n\t\tentry.sessionPath = sessionPath;\n\t}\n\n\t/** The live view a full budget is reconciled against; absent while the entry cannot report one. */\n\tprivate liveWorkerPaths(entry: RpcSessionEntry): LiveWorkerPaths | undefined {\n\t\tconst snapshot = entry.state === \"open\" ? entry.worker?.snapshot : undefined;\n\t\tif (!snapshot) return undefined;\n\t\treturn { livePaths: snapshot.liveSessionPaths, sessionPath: snapshot.sessionPath };\n\t}\n\n\tprivate openResult(handle: string, entry: RpcSessionEntry): OpenRpcSession {\n\t\tconst state = entry.worker?.snapshot?.state;\n\t\tif (!state) throw new RpcSessionRegistryError(\"open_failed\");\n\t\treturn { sessionId: handle, durableSessionId: state.sessionId, sessionPath: state.sessionFile };\n\t}\n}\n"]}
1
+ {"version":3,"file":"worker-session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/worker-session-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { ProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport type { CliRuntimeConfiguration } from \"../../main.ts\";\nimport {\n\ttype LiveWorkerPaths,\n\tRESERVATION_DENIAL_CODES,\n\tSessionPathReservations,\n} from \"./session-path-reservations.ts\";\nimport {\n\tfrozenProfile,\n\ttype OpenRpcSession,\n\ttype RpcSessionEntry,\n\ttype RpcSessionLaunchProfile,\n\ttype RpcSessionOpenOptions,\n\tRpcSessionRegistryError,\n\ttype RpcSessionRow,\n\tsessionIdentity,\n} from \"./session-registry.ts\";\nimport { SessionWorkerClient } from \"./session-worker-client.ts\";\nimport { SESSION_WORKER_LIMITS, type SessionWriteGrant } from \"./session-worker-protocol.ts\";\n\ntype SessionWorkerCallbacks = ConstructorParameters<typeof SessionWorkerClient>[0];\n\nexport interface WorkerSessionRegistryOptions {\n\treadonly configuration: CliRuntimeConfiguration;\n\treadonly closeGraceMs: number;\n\treadonly now: () => number;\n\t/** Production builds the real worker; a caller may supply one to drive a lifecycle path deterministically. */\n\treadonly createWorker?: (callbacks: SessionWorkerCallbacks) => SessionWorkerClient;\n}\n\n/** Transport-side lifecycle owner. Caller paths are never inspected on this event loop. */\nexport class WorkerSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new SessionPathReservations();\n\tprivate serial = 0;\n\treadonly closeGraceMs: number;\n\tprivate readonly now: () => number;\n\n\tprivate readonly options: WorkerSessionRegistryOptions;\n\tprivate readonly createWorker: (callbacks: SessionWorkerCallbacks) => SessionWorkerClient;\n\n\tconstructor(options: WorkerSessionRegistryOptions) {\n\t\tthis.options = options;\n\t\tthis.closeGraceMs = options.closeGraceMs;\n\t\tthis.now = options.now;\n\t\tthis.createWorker = options.createWorker ?? ((callbacks) => new SessionWorkerClient(callbacks));\n\t}\n\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\t/**\n\t * Memory admission is the in-process registry's concern: a worker-runtime session runs in\n\t * its own isolate and this registry already bounds occupancy with `too_many_sessions`.\n\t */\n\tsetWorkerAdmission(): void {}\n\n\tasync openSession(profile: RpcSessionLaunchProfile, options?: RpcSessionOpenOptions): Promise<OpenRpcSession> {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath)))\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\tif (profile.sessionPath) {\n\t\t\tconst key = this.knownReservationKey(profile.sessionPath);\n\t\t\tconst owner = key ? this.reservations.owner(key) : undefined;\n\t\t\tif (key && owner) return this.attach(owner, key, options);\n\t\t}\n\t\tif (this.size >= SESSION_WORKER_LIMITS.workers) throw new Error(\"too_many_sessions\");\n\t\tconst handle = `rpc-${++this.serial}`;\n\t\tconst storedProfile = frozenProfile(profile);\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\t...sessionIdentity(storedProfile),\n\t\t\tcwd: profile.cwd,\n\t\t\tattachments: 1,\n\t\t\tretainOnDisconnect: options?.retainOnDisconnect === true,\n\t\t\tlastCommandAt: this.now(),\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tlet workerFailure: string | undefined;\n\t\tconst worker = this.createWorker({\n\t\t\treserve: (path) => this.reserve(handle, path),\n\t\t\treconcile: (livePaths) => this.reconcile(handle, livePaths),\n\t\t\texit: () => {\n\t\t\t\tif (this.entries.get(handle) !== entry) return;\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tthis.reservations.releaseAll(handle);\n\t\t\t\tentry.closeResolve?.();\n\t\t\t},\n\t\t\tfailure: (error) => {\n\t\t\t\t// The open below only learns that its entry left `opening`, never why. Without this the\n\t\t\t\t// caller is told `session_closing` - a path whose owner is tearing down - for a worker\n\t\t\t\t// that died, and the actual reason reaches stderr alone (#1953).\n\t\t\t\tworkerFailure = error;\n\t\t\t\tentry.state = \"quarantined\";\n\t\t\t\tprocess.stderr.write(`senpi rpc session ${handle} quarantined: ${error}\\n`);\n\t\t\t},\n\t\t});\n\t\tentry.worker = worker;\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst path = await worker.prepare(this.options.configuration, profile);\n\t\t\tconst owner = this.reservations.owner(path);\n\t\t\tif (owner) {\n\t\t\t\tconst attached = this.attach(owner, path, options);\n\t\t\t\tentry.state = \"quarantined\";\n\t\t\t\tworker.quarantine();\n\t\t\t\treturn attached;\n\t\t\t}\n\t\t\tconst grant = this.reserve(handle, path);\n\t\t\tif (grant !== \"granted\") throw new RpcSessionRegistryError(RESERVATION_DENIAL_CODES[grant]);\n\t\t\tentry.reservationKey = path;\n\t\t\tentry.requestedPathKey = profile.sessionPath ? path : undefined;\n\t\t\tentry.sessionPath = path;\n\t\t\tconst snapshot = await worker.commit();\n\t\t\tif (entry.state !== \"opening\")\n\t\t\t\tthrow workerFailure === undefined\n\t\t\t\t\t? new RpcSessionRegistryError(\"session_closing\")\n\t\t\t\t\t: new RpcSessionRegistryError(\"open_failed\", workerFailure);\n\t\t\tentry.durableSessionId = snapshot.state.sessionId;\n\t\t\tentry.cwd = snapshot.state.cwd;\n\t\t\tentry.state = \"open\";\n\t\t\treturn this.openResult(handle, entry);\n\t\t} catch (cause) {\n\t\t\tentry.state = \"quarantined\";\n\t\t\tworker.quarantine();\n\t\t\tthrow cause;\n\t\t}\n\t}\n\n\tpeek(handle: string): RpcSessionEntry | undefined {\n\t\treturn this.entries.get(handle);\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (\n\t\t\tentry.state === \"quarantined\" ||\n\t\t\t(entry.state === \"closing\" &&\n\t\t\t\t![\"abort\", \"abort_bash\", \"extension_ui_response\", \"extension_ui_progress\"].includes(command))\n\t\t)\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.lastCommandAt = this.now();\n\t\treturn entry;\n\t}\n\n\tbeginClose(handle: string, onRole?: (finalizer: boolean) => void, options?: { detach?: boolean }): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" || entry.state === \"quarantined\") {\n\t\t\tonRole?.(false);\n\t\t\treturn entry;\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"opening\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.attachments--;\n\t\tif (entry.attachments > 0) return entry;\n\t\t// A retained session answers a client's detach by staying open at zero\n\t\t// attachments; only an explicit close or eviction reaches the worker.\n\t\tif (options?.detach && entry.retainOnDisconnect) {\n\t\t\tentry.attachments = 0;\n\t\t\treturn entry;\n\t\t}\n\t\tentry.state = \"closing\";\n\t\tentry.closeCompletion = new Promise((resolve) => {\n\t\t\tentry.closeResolve = resolve;\n\t\t});\n\t\tonRole?.(true);\n\t\treturn entry;\n\t}\n\n\tclose(handle: string): Promise<void> {\n\t\tconst entry = this.beginClose(handle);\n\t\treturn entry.state === \"closing\" ? this.closeMarked(handle) : Promise.resolve();\n\t}\n\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\" || !entry.worker) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.closeStarted) return entry.closeCompletion;\n\t\tentry.closeStarted = true;\n\t\t// Reply on a bounded deadline, but keep entry, attachments and reservations until exit.\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tawait Promise.race([\n\t\t\tentry.worker.close(this.closeGraceMs),\n\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (entry.state !== \"closed\") entry.state = \"quarantined\";\n\t\t\t\t\tresolve();\n\t\t\t\t}, this.closeGraceMs);\n\t\t\t}),\n\t\t]);\n\t\tif (timer) clearTimeout(timer);\n\t}\n\n\tlist(): RpcSessionRow[] {\n\t\treturn [...this.entries].map(([sessionId, entry]) => {\n\t\t\tconst state = entry.worker?.snapshot?.state;\n\t\t\treturn {\n\t\t\t\tsessionId,\n\t\t\t\tdurableSessionId: state?.sessionId ?? entry.durableSessionId,\n\t\t\t\tsessionPath: state?.sessionFile ?? entry.sessionPath,\n\t\t\t\tcwd: state?.cwd ?? entry.cwd,\n\t\t\t\tname: state?.sessionName,\n\t\t\t\tkind: entry.kind,\n\t\t\t\tcontext: entry.context,\n\t\t\t\t// A closing entry has already released its last attachment; never publish that as negative.\n\t\t\t\tattachments: Math.max(0, entry.attachments),\n\t\t\t\tstatus: entry.state === \"quarantined\" ? \"closing\" : entry.state,\n\t\t\t};\n\t\t});\n\t}\n\n\t/** Only compare spellings already tied to a granted identity; do not inspect caller paths here. */\n\tprivate knownReservationKey(path: string): string | undefined {\n\t\tif (this.reservations.owner(path)) return path;\n\t\tfor (const entry of this.entries.values()) {\n\t\t\t// An opening spelling maps only while the key granted for it is still held: a\n\t\t\t// superseded path belongs to nobody and must open its own worker.\n\t\t\tif (\n\t\t\t\tentry.profile.sessionPath === path &&\n\t\t\t\tentry.requestedPathKey &&\n\t\t\t\tthis.reservations.owner(entry.requestedPathKey)\n\t\t\t)\n\t\t\t\treturn entry.requestedPathKey;\n\t\t\tconst snapshot = entry.worker?.snapshot;\n\t\t\tif (snapshot?.state.sessionFile === path) return snapshot.sessionPath;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate attach(owner: string, path: string, options?: RpcSessionOpenOptions): OpenRpcSession {\n\t\tconst entry = this.entries.get(owner);\n\t\tif (entry?.state !== \"open\" || !entry.worker?.bindingReady || entry.worker.snapshot?.sessionPath !== path)\n\t\t\tthrow new RpcSessionRegistryError(\"session_path_in_use\");\n\t\tconst result = this.openResult(owner, entry);\n\t\tentry.attachments++;\n\t\t// Retention is a property of the live session: any attach may ask for it, and\n\t\t// no attach may revoke it for the clients that already rely on it.\n\t\tif (options?.retainOnDisconnect) entry.retainOnDisconnect = true;\n\t\tentry.lastCommandAt = this.now();\n\t\treturn { ...result, attached: true };\n\t}\n\n\t/** Granted paths currently held by a handle; the per-worker budget is bounded by it. */\n\treservationCount(handle: string): number {\n\t\treturn this.reservations.count(handle);\n\t}\n\n\tprivate reserve(handle: string, path: string): SessionWriteGrant {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) return \"conflict\";\n\t\tif (this.reservations.owner(path) === handle) return \"granted\";\n\t\tif (entry.state !== \"opening\" && entry.state !== \"open\") return \"conflict\";\n\t\treturn this.reservations.reserve(handle, path, this.liveWorkerPaths(entry));\n\t}\n\n\t/**\n\t * Releases the grants a worker's latest snapshot no longer claims.\n\t *\n\t * Only a fully open entry reports live writers. An entry still opening, closing or\n\t * quarantined keeps every grant until its real exit, because a worker stuck in a\n\t * syscall can still be writing a path it can no longer report.\n\t */\n\tprivate reconcile(handle: string, livePaths: readonly string[]): void {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") return;\n\t\tconst sessionPath = entry.worker?.snapshot?.sessionPath;\n\t\tthis.reservations.reconcile(handle, { livePaths, sessionPath });\n\t\tif (!sessionPath) return;\n\t\tentry.reservationKey = sessionPath;\n\t\tentry.sessionPath = sessionPath;\n\t}\n\n\t/** The live view a full budget is reconciled against; absent while the entry cannot report one. */\n\tprivate liveWorkerPaths(entry: RpcSessionEntry): LiveWorkerPaths | undefined {\n\t\tconst snapshot = entry.state === \"open\" ? entry.worker?.snapshot : undefined;\n\t\tif (!snapshot) return undefined;\n\t\treturn { livePaths: snapshot.liveSessionPaths, sessionPath: snapshot.sessionPath };\n\t}\n\n\tprivate openResult(handle: string, entry: RpcSessionEntry): OpenRpcSession {\n\t\tconst state = entry.worker?.snapshot?.state;\n\t\tif (!state) throw new RpcSessionRegistryError(\"open_failed\");\n\t\treturn { sessionId: handle, durableSessionId: state.sessionId, sessionPath: state.sessionFile };\n\t}\n}\n"]}
package/docs/providers.md CHANGED
@@ -22,6 +22,7 @@ Use `/login` in interactive mode, then select a provider:
22
22
  - GitHub Copilot
23
23
  - xAI (Grok/X subscription)
24
24
  - OpenRouter (OAuth-minted API key billed from OpenRouter credits)
25
+ - Kimi Code (kimi.com / kimi.ai subscriptions)
25
26
  - Radius
26
27
  - Cursor (Pro/Ultra/Teams) — authentication only for now, see below
27
28
 
@@ -136,6 +137,13 @@ Upstreams that require explicit cache breakpoints get `cache_control` blocks thr
136
137
 
137
138
  senpi sends `prompt_cache_key` (set to the session id) on Moonshot requests. Kimi documents the field as required for the Kimi Code Plan and recommended for any multi-turn agent ([Kimi context caching](https://platform.kimi.ai/docs/guide/use-context-caching-feature-of-kimi-api)). Kimi reports cache hits as a flat `usage.cached_tokens` field, which senpi parses as cache-read tokens.
138
139
 
140
+ ### Kimi Code
141
+
142
+ - Run `/login kimi-coding`, then select **Sign in with Kimi Code** and pick the service that hosts your account: **Mainland China (kimi.com)** or **Outside mainland China (kimi.ai)**
143
+ - The region is stored with the credential; token refresh and model requests follow it (`auth.kimi.ai` + `api.kimi.ai/coding` for international accounts), so a `.ai` login keeps working after restarts and env changes
144
+ - **Use an API key** asks the same region question and routes `KIMI_API_KEY` requests by it; `KIMI_CODE_REGION=global` (or `mainland-cn`) answers it for headless setups
145
+ - `KIMI_CODE_OAUTH_HOST` / `KIMI_OAUTH_HOST` still override the auth host for new logins and for credentials saved before regions existed; a host other than the two official ones is kept verbatim and needs a `models.json` `baseUrl` for inference
146
+
139
147
  ### Radius
140
148
 
141
149
  Radius is a dynamic `pi-messages` gateway. `/login radius` stores OAuth tokens in `auth.json`; the gateway catalog is refreshed independently and cached in `models-store.json`. Custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` and a gateway `baseUrl`.
@@ -286,7 +294,7 @@ senpi
286
294
  | Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
287
295
  | Together AI | `TOGETHER_API_KEY` | `together` |
288
296
  | Baseten | `BASETEN_API_KEY` | `baseten` |
289
- | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` |
297
+ | Kimi For Coding | `KIMI_API_KEY` (+ `KIMI_CODE_REGION`) | `kimi-coding` |
290
298
  | MiniMax | `MINIMAX_API_KEY` | `minimax` |
291
299
  | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |
292
300
  | Qwen Token Plan (existing catalog) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |
package/docs/rpc.md CHANGED
@@ -207,7 +207,13 @@ are what this decision encodes; the other two are what a client must not undo el
207
207
  `ensureHost` records a `writer: { pid, startTime }` stamp in its pidfile and stops the host it names only when
208
208
  that stamp is this process (the start time is what keeps a recycled pid from inheriting the right). A pidfile
209
209
  written by anyone else fails the ensure with `HostEnsureRefusedError { reason: "foreign_writer" }` and the host
210
- keeps running.
210
+ keeps running. That refusal is about signalling, so it holds only while something still accepts connections at
211
+ the public path: a registration that names a live process whose entry is GONE (it was replaced by another socket
212
+ and the replacement later exited) or whose entry nobody listens behind (a dead listener left it, a successor's
213
+ rename never landed) describes a generation nobody can reach, and binding a fresh generation to the free path
214
+ touches nothing of it. The ensure then starts one beside it, numbered after the stranded generation, which
215
+ keeps its registration until it exits; refusing there locked every client out until that process happened to
216
+ end (#1936). A named pipe has no entry to lose, so win32 keeps refusing.
211
217
  - **I2 - compatibility is protocol version + capabilities, never semver equality.** An ordinal that cannot be
212
218
  compared (a build without git metadata, a host that reports none) is EQUAL, and since a handoff requires
213
219
  STRICTLY greater, such a pair attaches instead of upgrading.
@@ -238,10 +244,11 @@ client's sessions. A GENERATION HANDOFF replaces the process while its sessions
238
244
  running host keeps serving.
239
245
  3. `handoffHost` then registers the successor (its own `generations/<instanceId>/host.pid`, the pointer
240
246
  moved onto it, and `settings.json { socket, generation }`) and sends
241
- the predecessor SIGUSR1 = DRAIN: stop accepting, keep every connection already proxied, park each
242
- retained session as soon as its turn settles, and exit through the ordinary idle path. Attached clients
243
- see `session_closed { reason: "handoff_parked" }` for a parked session and reopen it with
244
- `open_session { sessionPath }`.
247
+ the predecessor SIGUSR1 = DRAIN: stop accepting, announce `host_superseded` to every connection,
248
+ park attached and unattached sessions as their turns and requests settle, then exit independently of
249
+ connection occupancy. Clients see `session_closed { reason: "handoff_parked", sessionPath }` before
250
+ their connection closes and reopen with `open_session { sessionPath }` on the successor. Losing
251
+ ownership of the public socket triggers the same sequence even if SIGUSR1 never arrives.
245
252
 
246
253
  Two guards decide whether a handoff is attempted at all, and both fail closed:
247
254
 
@@ -272,6 +279,13 @@ the moment it does not, it applies the same drain to itself: park each retained
272
279
  release its claims, and exit when it holds no attached session. Nothing else changes - a session mid-turn
273
280
  still finishes, and a client still attached is still served.
274
281
 
282
+ That check separates a name that was TAKEN OVER from a name that is GONE, because the evidence differs in
283
+ strength. A different entry at the path proves supersession on sight and drains immediately. An absent entry
284
+ proves only that nobody is serving the name, so it has to be seen on several consecutive checks before it
285
+ counts, and a check that cannot answer resets the count rather than adding to it. Either way the outcome is
286
+ the same drain: an unlinked socket name can never accept a connection again, so the generation behind it is
287
+ unreachable no matter why the name went away.
288
+
275
289
  `stopHost({ socket, agentDir, drain?, force? })` ends a generation: `drain: true` is always permitted (it
276
290
  ends no work, it only stops the host from taking new work), while a hard stop requires a host that reports
277
291
  no open sessions, or `force: true`. A hard stop also drops that generation's registration (the pointer and
@@ -485,7 +499,7 @@ exposing session content. The one exception is `session_closed` and `session_par
485
499
  `kind: "worker"`: they are delivered only to
486
500
  the connections attached to that session, so machine-driven work neither appears in nor disappears from a client that never
487
501
  asked for it. Every other lifecycle record, and every record of an `interactive` session, keeps today's broadcast. Host-level
488
- records (`host_stalled`, `host_memory_pressure`) are broadcast the same way and describe the host process rather than a
502
+ records (`host_superseded`, `host_stalled`, `host_memory_pressure`) are broadcast the same way and describe the host process rather than a
489
503
  session. Correlated responses and dialog extension UI
490
504
  requests (select, confirm, input, and editor) are requester-only; other extension UI state records go to the session's
491
505
  attached connections. To observe a foreign session, open it by its existing
@@ -586,7 +600,12 @@ Environment overrides beat the file, and invalid values fall through to the next
586
600
  (`transient`|`persistent`) and `SENPI_RPC_HOST_IDLE_EXIT_MS` (positive integer milliseconds).
587
601
 
588
602
  The host exits only after the window elapses with NO attached client connections and NO active turns — continuously.
589
- Any connection or agent turn resets the window, so a busy host never exits, and the exit itself is clean: the RPC host
603
+ Any connection or agent turn resets the window, so a busy host never exits. The supervisor learns about turns through
604
+ its observer connection to the host; while that connection is unhealthy it cannot see turns, so it treats activity as
605
+ unknown and keeps the host open as if a turn were running — but only for one idle window, during which it keeps
606
+ reconnecting. An observer that stays unhealthy for longer than the window stops counting as busy, and the connection
607
+ count alone decides from there. A `persistent` host has an infinite window and so keeps its infinite benefit of the
608
+ doubt. The exit itself is clean: the RPC host
590
609
  receives SIGTERM first, flushes pending output, removes its socket, and the supervisor then removes `host.pid` and
591
610
  `settings.json` (the stderr log stays for diagnostics). After an idle exit, the next `ensureHost()` transparently
592
611
  starts a fresh host. `get_protocol_info` over the public socket behaves exactly as before; the supervisor is
@@ -599,6 +618,48 @@ internal directory. The supervisor also exports `SENPI_RPC_HOST_WATCH_PPID` as a
599
618
  set only by the supervisor: a host started any other way (plain `senpi --mode rpc --listen …`, embedders, hand-started
600
619
  hosts) sees neither variable and is unaffected. A host whose supervisor is alive is never touched by this binding.
601
620
 
621
+ ### Supersession lifecycle: `host_superseded`
622
+
623
+ Before parking for a generation handoff, the old host sends every connected client one unsolicited record:
624
+
625
+ ```json
626
+ {"type":"host_superseded","instanceId":"old-host-uuid","generation":0,"successor":{"socket":"/tmp/agent/rpc.sock"}}
627
+ ```
628
+
629
+ `instanceId` and `generation` identify the OLD host, matching its `get_protocol_info` response. `successor.socket`
630
+ identifies the public socket, never the supervisor's private hop. `successor` is `null` when a drain is requested
631
+ without a known replacement. This record has no request id or session handle; older clients can ignore it.
632
+
633
+ 1. The announcement is queued on every connection before handoff parking changes any session.
634
+ 2. Every parkable session, attached or unattached, is parked immediately. A session is **parkable** when no agent
635
+ run, bash/tool execution, compaction, queued session work, or in-flight host request remains. Accepted opens,
636
+ binding creation, close requests, and steer delivery must finish. This is the ordinary busy predicate with
637
+ **durable wake-source holds excluded**: persistent monitors, scheduled continuations and goal-loop holds do not
638
+ pin an old generation. They resume from the persisted session when reopened, as after a restart. Ordinary idle
639
+ eviction retains its broader activity predicate, including all wake sources.
640
+ 3. Active sessions keep serving their existing connections and park when their turns and requests settle. Each
641
+ released handle emits `session_closed { sessionId, reason: "handoff_parked", sessionPath }` after releasing its
642
+ file. This is not a session deletion: reopen `sessionPath` on the successor to obtain a new routing handle.
643
+ That `sessionPath` is the CANONICAL path the host reserved the file under, which is not necessarily the string
644
+ the client passed to `open_session` (a scratch dir under `/var/...` on macOS is reported as `/private/var/...`).
645
+ A client matching the record against its own stored path must canonicalize before comparing; reopening with
646
+ either spelling resolves to the same reservation.
647
+ The connection closes after its last attached session parks; a connection shared with another active session
648
+ stays open until that session also settles. A command racing parking on the old connection is either served
649
+ or answered by that terminal record and close, never `unknown_session` for the parked handle. New opens on a
650
+ draining connection are refused with `host_draining`.
651
+ 4. The **supervisor** starts `SENPI_RPC_HANDOFF_GRACE_MS` at supersession (default **600000 ms / 10 minutes**).
652
+ This is a soft grace, not an abort deadline or a delay before parking. At expiry it reports outstanding work
653
+ and requests another drain pass. Sessions still mid-turn or handling requests remain alive even beyond grace;
654
+ no turn is cut short. Parkable sessions never wait for grace. A bare socket host has no supervisor grace timer,
655
+ but follows the same announce/park/settle sequence.
656
+ 5. Once every session has parked and its terminal records have drained, the generation exits even if an observer
657
+ or client never voluntarily disconnects. Neither the normal idle timeout nor persistent-host mode can pin it.
658
+
659
+ A client may react to `host_superseded` early, but the old generation retains each file until its work settles;
660
+ `session_path_in_use` on the successor remains a retryable response until parking releases that claim. A client
661
+ that ignores the announcement still receives the terminal record and connection close.
662
+
602
663
  ### Shared host occupancy (idle eviction, retention, empty-host exit)
603
664
 
604
665
  **The daemon does not cap sessions.** On the in-process runtime - the default for a `--listen` socket host, i.e. the
@@ -619,8 +680,8 @@ What the host does enforce are lifecycle windows, and they only ever return memo
619
680
  `session_parked { sessionId, sessionPath }` and there is no `close_session` response, because nothing closed the session -
620
681
  the routing handle was released while the session itself stays on disk and reopens with `open_session { sessionPath }`
621
682
  (as a NEW handle). A client that does not know `session_parked` ignores it and learns the handle is gone from its next
622
- command's `unknown_session`. A GENERATION HANDOFF parks the same sessions for the same reason, but names itself in
623
- the record it emits: `session_closed { sessionId, reason: "handoff_parked" }`, so a client can tell "the host handed
683
+ command's `unknown_session`. A GENERATION HANDOFF also parks attached sessions, using the distinct activity
684
+ predicate below, and emits `session_closed { sessionId, reason: "handoff_parked", sessionPath }`, so a client can tell "the host handed
624
685
  over, reopen by path" from "this session ended". An explicit `close_session` is `reason: "client_close"`; the host
625
686
  process exiting is `reason: "host_shutdown"` (even for a retained session - the process is going away, so this is not
626
687
  a park). `reason` is optional on the wire; a client that does not know a
@@ -831,11 +892,12 @@ Response-level `sessionId` = opaque **routing handle**, unique per process epoch
831
892
  In the response `error` field, machine-matchable:
832
893
 
833
894
  - `unknown_session`
834
- - `session_closing`
895
+ - `session_closing` (an entry whose owner is tearing down). A worker that FAILS while its session is being opened is not that case and reports `open_failed` carrying the worker's own reason, so the code a client matches on always names what actually happened.
835
896
  - `session_path_in_use` (path held by an opening or quarantined owner; a fully-open current owner is attached instead, an owner whose teardown is already in flight is waited out on the in-process runtime, and a path a live owner has superseded is released rather than held). A path held by ANOTHER GENERATION of the daemon carries `errorData { owner: { instanceId, pid, processStartTime, sessionPath, current }, retry_after_ms: 2000 }`: that generation is still writing the file and is parking it, so the open is a retry, not a failure. Only a claim whose owner serves the socket (`current: true`) or still has a client attached refuses an open at all - a superseded, attachment-less claim is reclaimed instead
836
897
  - `session_reservation_limit` (this worker already holds 64 live session paths; the open or session replacement was refused without disturbing the existing session)
837
898
  - `missing_session_id` (session-scoped command without `sessionId` in multi mode)
838
899
  - `multi_session_disabled` (`open_session` in classic mode)
900
+ - `host_draining` (`open_session` on a connection whose generation is parking for a handoff; the successor already owns the public path, so re-resolve it and open there rather than retrying this connection)
839
901
  - `invalid_path` (relative `sessionPath`/`cwd`)
840
902
  - `open_failed: <detail>`
841
903
  - `invalid_session_context: <detail>` (`open_session.context` past a documented cap: more than 32 keys, a key that does not match `^[a-z][a-z0-9_]*$`, a non-string or >16 KiB value, or more than 32 KiB of JSON in total; the detail names the cap and its byte budget)
package/docs/settings.md CHANGED
@@ -88,7 +88,7 @@ Permission rules are a confirmation policy, not a sandbox. Senpi, extensions, pa
88
88
  | `modelThinkingLevels` | object | - | Per-model reasoning effort memory (`"provider/id": "level"`) |
89
89
  | `modelLastOnThinkingLevels` | object | - | Per-model last non-off reasoning level, used by `/reasoning on` to restore the previous effort |
90
90
  | `modelServiceTiers` | object | - | Per-model service tier memory (`"provider/id": "auto" \| "priority"`) |
91
- | `promptPreset` | string | `"auto"` | Force a system prompt preset: `"auto"`, `"kimi-k2-6"`, `"kimi-k2-7"`, `"kimi-k2-8"`, `"kimi-k3"`, `"glm-5.2"`, `"glm-5.3"`, `"grok-4.5"`, `"grok-4.6"`, `"claude-fable-5"`, `"claude-fable-5-1"`, `"claude-opus-5"`, `"claude-opus-4-5"`, `"claude-opus-4-6"`, `"claude-opus-4-7"`, `"claude-opus-4-8"`, `"deepseek-v4-flash"`, `"deepseek-v4-flash-0731"`, `"deepseek-v4-1-flash"`, `"deepseek-v4-pro"`, `"gpt-5"`, `"gpt-5.2"`, `"gpt-5.3-codex"`, `"gpt-5.4"`, `"gpt-5.5"`, `"gpt-5.6"`, or `"gpt-6-astra"` |
91
+ | `promptPreset` | string | `"auto"` | Force a system prompt preset: `"auto"`, `"kimi-k2-6"`, `"kimi-k2-7"`, `"kimi-k2-8"`, `"kimi-k3"`, `"glm-5.2"`, `"glm-5.3"`, `"grok-4.5"`, `"grok-4.6"`, `"grok-4.7"`, `"claude-fable-5"`, `"claude-fable-5-1"`, `"claude-opus-5"`, `"claude-opus-4-5"`, `"claude-opus-4-6"`, `"claude-opus-4-7"`, `"claude-opus-4-8"`, `"deepseek-v4-flash"`, `"deepseek-v4-flash-0731"`, `"deepseek-v4-1-flash"`, `"deepseek-v4-pro"`, `"gpt-5"`, `"gpt-5.2"`, `"gpt-5.3-codex"`, `"gpt-5.4"`, `"gpt-5.5"`, `"gpt-5.6"`, or `"gpt-6-astra"` |
92
92
  | `hideThinkingBlock` | boolean | `false` | Hide thinking blocks in output |
93
93
  | `showCacheMissNotices` | boolean | `false` | Show transcript notices for significant prompt-cache misses, compaction or branch-summary usage, and provider recovery diagnostics such as dropped Anthropic thinking blocks |
94
94
  | `thinkingBudgets` | object | - | Custom token budgets per thinking level. Anthropic, Google, and Bedrock use these natively. OpenAI-compatible models use them when `compat.thinkingTokenBudgetField` (or `supportsThinkingTokenBudget`) is set. |
@@ -12,6 +12,35 @@
12
12
 
13
13
  ### Removed
14
14
 
15
+ ## [2026.9.22-2] - 2026-09-22
16
+
17
+ ### Breaking Changes
18
+
19
+ ### Added
20
+
21
+ ### Changed
22
+
23
+ ### Fixed
24
+
25
+ - `wake_source_state` (live detached eval cells) is now published on the rpc channel as well as the in-process event bus, so out-of-process consumers see live cells the way the TUI footer does (#1943).
26
+
27
+ ### Removed
28
+
29
+ ## [2026.9.22] - 2026-09-21
30
+
31
+ ### Breaking Changes
32
+
33
+ ### Added
34
+
35
+ - `eval` runs up to `maxDetachedCells` background cells per session (default 15, `SENPI_CODEMODE_MAX_DETACHED_CELLS`) and queues same-language cells on their kernel instead of rejecting them; `list` observes live and recent cells, and resetting a busy language refuses with `eval_kernel_busy_reset_refused` instead of stopping live work. ([#1908](https://github.com/code-yeongyu/senpi/issues/1908))
36
+ - `agent()` forwards `isolated`, `apply`, and `merge` when the task host advertises isolation. Hosts that do not still drop those options with the existing warning. A foreground call whose isolation did not apply now raises instead of looking successful; with `handle: true` the isolation result arrives on completion. ([#1910](https://github.com/code-yeongyu/senpi/issues/1910))
37
+
38
+ ### Changed
39
+
40
+ ### Fixed
41
+
42
+ ### Removed
43
+
15
44
  ## [2026.9.21-2] - 2026-09-21
16
45
 
17
46
  ### Breaking Changes