@code-yeongyu/senpi 2026.9.27-2 → 2026.9.27-3

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 (293) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/bundle/chunks/anthropic-messages-3FGAXYJS.js +8 -0
  3. package/dist/bundle/chunks/{app-server-command-ATWHNOOO.js → app-server-command-ZVB35UIJ.js} +1 -1
  4. package/dist/bundle/chunks/azure-openai-responses-4SUOOZZ4.js +2 -0
  5. package/dist/bundle/chunks/{chunk-KMJXSXRX.js → chunk-357I4IUU.js} +2 -2
  6. package/dist/bundle/chunks/chunk-3SIQIY57.js +8 -0
  7. package/dist/bundle/chunks/chunk-45SZOLCU.js +2 -0
  8. package/dist/bundle/chunks/{chunk-77ACEMYL.js → chunk-6J6RDMBY.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-FD34HSJJ.js → chunk-C6LQNMRB.js} +1 -1
  10. package/dist/bundle/chunks/chunk-ELM77A26.js +2 -0
  11. package/dist/bundle/chunks/chunk-GZVCRV7I.js +3 -0
  12. package/dist/bundle/chunks/{chunk-TTY5BHCG.js → chunk-H2SGCPMD.js} +1 -1
  13. package/dist/bundle/chunks/chunk-KLBKUBDV.js +2 -0
  14. package/dist/bundle/chunks/chunk-L6QV7NZI.js +2 -0
  15. package/dist/bundle/chunks/{chunk-ZKZAATFM.js → chunk-LDB5UMH2.js} +2 -2
  16. package/dist/bundle/chunks/{chunk-HZAKOVHN.js → chunk-LGKKU4VM.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-6Q6DGME2.js → chunk-NMDALHXH.js} +1 -1
  18. package/dist/bundle/chunks/chunk-NNILBBOH.js +18 -0
  19. package/dist/bundle/chunks/{chunk-SUQDU5KM.js → chunk-OQRUK2DK.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-67BC7VZV.js → chunk-OZN2KUPR.js} +6 -6
  21. package/dist/bundle/chunks/{chunk-X3T45623.js → chunk-QK4QKRTF.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-WM5FVFAP.js → chunk-QPZVJQC5.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-HPY47AVX.js → chunk-QYAO24GB.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-6W2DB2GC.js → chunk-RULGEHQM.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-6SLQ5HFO.js → chunk-RYI6XR6Z.js} +55 -53
  26. package/dist/bundle/chunks/chunk-U3VOXCGP.js +2 -0
  27. package/dist/bundle/chunks/{chunk-LPFZGWG2.js → chunk-Y7EIVDHZ.js} +1 -1
  28. package/dist/bundle/chunks/chunk-ZIVYE5YK.js +2 -0
  29. package/dist/bundle/chunks/cli-main-RONHLFWZ.js +3 -0
  30. package/dist/bundle/chunks/google-generative-ai-ZTGKZTGU.js +2 -0
  31. package/dist/bundle/chunks/google-vertex-CCZ376YK.js +2 -0
  32. package/dist/bundle/chunks/help-fast-path-5LRG6FCG.js +2 -0
  33. package/dist/bundle/chunks/host-command-JVPGQR74.js +2 -0
  34. package/dist/bundle/chunks/host-lifecycle-R4HLXLBM.js +3 -0
  35. package/dist/bundle/chunks/host-lifecycle.js +4 -4
  36. package/dist/bundle/chunks/interactive-host-runtime-FES2G62G.js +4 -0
  37. package/dist/bundle/chunks/interactive-mode-45EP2FVL.js +2 -0
  38. package/dist/bundle/chunks/{list-tips-EBRNBPBM.js → list-tips-XXMRDHN6.js} +1 -1
  39. package/dist/bundle/chunks/{multi-session-host-CE3W6COI.js → multi-session-host-EFCMD5OT.js} +6 -6
  40. package/dist/bundle/chunks/{openai-completions-4CLXDDHJ.js → openai-completions-OP6VFNUR.js} +1 -1
  41. package/dist/bundle/chunks/openai-images-6U5FCN5S.js +2 -0
  42. package/dist/bundle/chunks/openai-responses-7PWMBGYX.js +2 -0
  43. package/dist/bundle/chunks/openrouter-images-QIKWQZUD.js +2 -0
  44. package/dist/bundle/chunks/{package-manager-cli-2PHRL2SX.js → package-manager-cli-5XJS3TYM.js} +1 -1
  45. package/dist/bundle/chunks/rotation-stream-KW7PTEC4.js +2 -0
  46. package/dist/bundle/chunks/rpc-mode-ZHYECU24.js +2 -0
  47. package/dist/bundle/chunks/session-picker-QHIYVQP4.js +2 -0
  48. package/dist/bundle/chunks/session-worker.js +245 -238
  49. package/dist/bundle/chunks/{state-store-Q5EWEAFG.js → state-store-D4GGGT3J.js} +1 -1
  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/cli/continue-moved.d.ts +15 -0
  54. package/dist/cli/continue-moved.d.ts.map +1 -0
  55. package/dist/cli/continue-moved.js +26 -0
  56. package/dist/cli/continue-moved.js.map +1 -0
  57. package/dist/core/agent-session-runtime.d.ts +3 -0
  58. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  59. package/dist/core/agent-session-runtime.js +43 -10
  60. package/dist/core/agent-session-runtime.js.map +1 -1
  61. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +5 -1
  63. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js.map +1 -1
  64. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.d.ts +13 -1
  65. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.d.ts.map +1 -1
  66. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.js +25 -2
  67. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-claim.js.map +1 -1
  68. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +1 -0
  69. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts.map +1 -1
  70. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.js.map +1 -1
  71. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -0
  72. package/dist/core/extensions/builtin/mcp/catalog.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/mcp/catalog.js +9 -0
  74. package/dist/core/extensions/builtin/mcp/catalog.js.map +1 -1
  75. package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
  76. package/dist/core/extensions/builtin/mcp/connection-types.d.ts.map +1 -1
  77. package/dist/core/extensions/builtin/mcp/connection-types.js.map +1 -1
  78. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -1
  79. package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
  80. package/dist/core/extensions/builtin/mcp/connection.js +3 -2
  81. package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
  82. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +3 -0
  83. package/dist/core/extensions/builtin/mcp/expose/session.d.ts.map +1 -1
  84. package/dist/core/extensions/builtin/mcp/expose/session.js +9 -2
  85. package/dist/core/extensions/builtin/mcp/expose/session.js.map +1 -1
  86. package/dist/core/extensions/builtin/mcp/service-register.d.ts.map +1 -1
  87. package/dist/core/extensions/builtin/mcp/service-register.js +7 -1
  88. package/dist/core/extensions/builtin/mcp/service-register.js.map +1 -1
  89. package/dist/core/extensions/builtin/mcp/service-tools-changed.d.ts +18 -0
  90. package/dist/core/extensions/builtin/mcp/service-tools-changed.d.ts.map +1 -0
  91. package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -0
  92. package/dist/core/extensions/builtin/mcp/service-tools-changed.js.map +1 -0
  93. package/dist/core/extensions/builtin/mcp/service-types.d.ts +13 -0
  94. package/dist/core/extensions/builtin/mcp/service-types.d.ts.map +1 -1
  95. package/dist/core/extensions/builtin/mcp/service-types.js.map +1 -1
  96. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  97. package/dist/core/extensions/builtin/mcp/service.js +4 -37
  98. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  99. package/dist/core/extensions/builtin/mcp/shared-lease.js +1 -1
  100. package/dist/core/extensions/builtin/mcp/shared-lease.js.map +1 -1
  101. package/dist/core/extensions/builtin/mcp/startup-race.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/mcp/startup-race.js +11 -1
  103. package/dist/core/extensions/builtin/mcp/startup-race.js.map +1 -1
  104. package/dist/core/moved-sessions.d.ts +10 -0
  105. package/dist/core/moved-sessions.d.ts.map +1 -0
  106. package/dist/core/moved-sessions.js +84 -0
  107. package/dist/core/moved-sessions.js.map +1 -0
  108. package/dist/core/process-crash-record.d.ts +62 -0
  109. package/dist/core/process-crash-record.d.ts.map +1 -0
  110. package/dist/core/process-crash-record.js +216 -0
  111. package/dist/core/process-crash-record.js.map +1 -0
  112. package/dist/core/resume-target.d.ts +20 -0
  113. package/dist/core/resume-target.d.ts.map +1 -0
  114. package/dist/core/resume-target.js +30 -0
  115. package/dist/core/resume-target.js.map +1 -0
  116. package/dist/core/session-discovery.d.ts.map +1 -1
  117. package/dist/core/session-discovery.js +1 -0
  118. package/dist/core/session-discovery.js.map +1 -1
  119. package/dist/core/session-hold-errors.d.ts +20 -0
  120. package/dist/core/session-hold-errors.d.ts.map +1 -0
  121. package/dist/core/session-hold-errors.js +28 -0
  122. package/dist/core/session-hold-errors.js.map +1 -0
  123. package/dist/core/session-holders.d.ts +22 -0
  124. package/dist/core/session-holders.d.ts.map +1 -0
  125. package/dist/core/session-holders.js +234 -0
  126. package/dist/core/session-holders.js.map +1 -0
  127. package/dist/core/session-manager.d.ts +5 -0
  128. package/dist/core/session-manager.d.ts.map +1 -1
  129. package/dist/core/session-manager.js.map +1 -1
  130. package/dist/core/session-rebind.d.ts +7 -1
  131. package/dist/core/session-rebind.d.ts.map +1 -1
  132. package/dist/core/session-rebind.js +21 -5
  133. package/dist/core/session-rebind.js.map +1 -1
  134. package/dist/core/session-summary-index-file.d.ts.map +1 -1
  135. package/dist/core/session-summary-index-file.js +5 -2
  136. package/dist/core/session-summary-index-file.js.map +1 -1
  137. package/dist/core/session-summary.d.ts +3 -0
  138. package/dist/core/session-summary.d.ts.map +1 -1
  139. package/dist/core/session-summary.js +7 -0
  140. package/dist/core/session-summary.js.map +1 -1
  141. package/dist/legacy-dir-copy.d.ts +9 -0
  142. package/dist/legacy-dir-copy.d.ts.map +1 -0
  143. package/dist/legacy-dir-copy.js +69 -0
  144. package/dist/legacy-dir-copy.js.map +1 -0
  145. package/dist/legacy-senpi-dir-migration.d.ts.map +1 -1
  146. package/dist/legacy-senpi-dir-migration.js +64 -48
  147. package/dist/legacy-senpi-dir-migration.js.map +1 -1
  148. package/dist/main.d.ts.map +1 -1
  149. package/dist/main.js +45 -19
  150. package/dist/main.js.map +1 -1
  151. package/dist/migrations-state.d.ts +1 -1
  152. package/dist/migrations-state.d.ts.map +1 -1
  153. package/dist/migrations-state.js +5 -1
  154. package/dist/migrations-state.js.map +1 -1
  155. package/dist/migrations.d.ts.map +1 -1
  156. package/dist/migrations.js +4 -0
  157. package/dist/migrations.js.map +1 -1
  158. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/session-selector.js +5 -3
  160. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  161. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  162. package/dist/modes/interactive/interactive-mode.js +8 -4
  163. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  164. package/dist/modes/interactive/resume-rebind.d.ts +12 -0
  165. package/dist/modes/interactive/resume-rebind.d.ts.map +1 -0
  166. package/dist/modes/interactive/resume-rebind.js +37 -0
  167. package/dist/modes/interactive/resume-rebind.js.map +1 -0
  168. package/dist/modes/interactive/tips/catalog/computer-tips.d.ts +7 -0
  169. package/dist/modes/interactive/tips/catalog/computer-tips.d.ts.map +1 -0
  170. package/dist/modes/interactive/tips/catalog/computer-tips.js +36 -0
  171. package/dist/modes/interactive/tips/catalog/computer-tips.js.map +1 -0
  172. package/dist/modes/interactive/tips/registry.d.ts.map +1 -1
  173. package/dist/modes/interactive/tips/registry.js +2 -0
  174. package/dist/modes/interactive/tips/registry.js.map +1 -1
  175. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  176. package/dist/modes/rpc/connection-handler.js +3 -0
  177. package/dist/modes/rpc/connection-handler.js.map +1 -1
  178. package/dist/modes/rpc/host-child-exit.d.ts.map +1 -1
  179. package/dist/modes/rpc/host-child-exit.js +4 -0
  180. package/dist/modes/rpc/host-child-exit.js.map +1 -1
  181. package/dist/modes/rpc/host-crash-record.d.ts +8 -0
  182. package/dist/modes/rpc/host-crash-record.d.ts.map +1 -1
  183. package/dist/modes/rpc/host-crash-record.js.map +1 -1
  184. package/dist/modes/rpc/host-crash-record.test.js +11 -0
  185. package/dist/modes/rpc/host-crash-record.test.js.map +1 -1
  186. package/dist/modes/rpc/rpc-client.d.ts +2 -2
  187. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  188. package/dist/modes/rpc/rpc-client.js +14 -6
  189. package/dist/modes/rpc/rpc-client.js.map +1 -1
  190. package/dist/modes/rpc/rpc-request-deadline.d.ts +17 -0
  191. package/dist/modes/rpc/rpc-request-deadline.d.ts.map +1 -0
  192. package/dist/modes/rpc/rpc-request-deadline.js +27 -0
  193. package/dist/modes/rpc/rpc-request-deadline.js.map +1 -0
  194. package/dist/modes/rpc/rpc-types.d.ts +9 -2
  195. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  196. package/dist/modes/rpc/rpc-types.js.map +1 -1
  197. package/dist/modes/rpc/session-command-router.d.ts +3 -6
  198. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  199. package/dist/modes/rpc/session-command-router.js +9 -22
  200. package/dist/modes/rpc/session-command-router.js.map +1 -1
  201. package/dist/modes/rpc/session-path-key.d.ts +10 -0
  202. package/dist/modes/rpc/session-path-key.d.ts.map +1 -0
  203. package/dist/modes/rpc/session-path-key.js +33 -0
  204. package/dist/modes/rpc/session-path-key.js.map +1 -0
  205. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  206. package/dist/modes/rpc/session-registry.js +6 -10
  207. package/dist/modes/rpc/session-registry.js.map +1 -1
  208. package/dist/modes/rpc/session-sweep.d.ts +18 -0
  209. package/dist/modes/rpc/session-sweep.d.ts.map +1 -0
  210. package/dist/modes/rpc/session-sweep.js +32 -0
  211. package/dist/modes/rpc/session-sweep.js.map +1 -0
  212. package/dist/pi-dir-restore.d.ts +4 -0
  213. package/dist/pi-dir-restore.d.ts.map +1 -0
  214. package/dist/pi-dir-restore.js +92 -0
  215. package/dist/pi-dir-restore.js.map +1 -0
  216. package/docs/rpc.md +8 -3
  217. package/docs/sdk.md +29 -0
  218. package/docs/sessions.md +4 -0
  219. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  221. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +2 -2
  222. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  223. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +7 -0
  224. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  225. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts.map +1 -1
  226. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js +3 -1
  227. package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js.map +1 -1
  228. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +3 -1
  229. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  230. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  231. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  232. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +10 -7
  234. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +9 -1
  237. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  238. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  239. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  241. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.d.ts +33 -0
  243. package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.d.ts.map +1 -0
  244. package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.js +56 -0
  245. package/node_modules/@earendil-works/pi-ai/dist/provider-diagnostic.js.map +1 -0
  246. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  247. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +3 -0
  249. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  251. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.d.ts +5 -0
  252. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.d.ts.map +1 -0
  253. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.js +18 -0
  254. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-carrier.js.map +1 -0
  255. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.d.ts +26 -0
  256. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.d.ts.map +1 -0
  257. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.js +103 -0
  258. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-sources.js.map +1 -0
  259. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.d.ts +36 -0
  260. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.d.ts.map +1 -0
  261. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.js +129 -0
  262. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-diagnostic-vocabulary.js.map +1 -0
  263. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js +5 -4
  265. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  267. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  268. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  269. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  270. package/package.json +7 -7
  271. package/dist/bundle/chunks/anthropic-messages-UYDVRFAS.js +0 -8
  272. package/dist/bundle/chunks/azure-openai-responses-DODWR4W6.js +0 -2
  273. package/dist/bundle/chunks/chunk-2L26KPZ6.js +0 -8
  274. package/dist/bundle/chunks/chunk-3CC5ZOBV.js +0 -13
  275. package/dist/bundle/chunks/chunk-L6JO4M6J.js +0 -3
  276. package/dist/bundle/chunks/chunk-SFYHCFRO.js +0 -2
  277. package/dist/bundle/chunks/chunk-TTHMOL5G.js +0 -2
  278. package/dist/bundle/chunks/chunk-UYCGOK6M.js +0 -2
  279. package/dist/bundle/chunks/chunk-ZTVQY5V6.js +0 -2
  280. package/dist/bundle/chunks/cli-main-HOM6UH3H.js +0 -3
  281. package/dist/bundle/chunks/google-generative-ai-VIEFECKJ.js +0 -2
  282. package/dist/bundle/chunks/google-vertex-Q64CKFID.js +0 -2
  283. package/dist/bundle/chunks/help-fast-path-YGYHMV2W.js +0 -2
  284. package/dist/bundle/chunks/host-command-XXSAFWBW.js +0 -2
  285. package/dist/bundle/chunks/host-lifecycle-GOMGJ25O.js +0 -3
  286. package/dist/bundle/chunks/interactive-host-runtime-IYCZWJGX.js +0 -4
  287. package/dist/bundle/chunks/interactive-mode-ZMO7YVYU.js +0 -2
  288. package/dist/bundle/chunks/openai-images-JA5N5DUU.js +0 -2
  289. package/dist/bundle/chunks/openai-responses-IBFGBAJP.js +0 -2
  290. package/dist/bundle/chunks/openrouter-images-MW5TXV7L.js +0 -2
  291. package/dist/bundle/chunks/rotation-stream-NATO6IMN.js +0 -2
  292. package/dist/bundle/chunks/rpc-mode-WCETTMFV.js +0 -2
  293. package/dist/bundle/chunks/session-picker-BP7THCWF.js +0 -2
@@ -0,0 +1,62 @@
1
+ /** Crash records kept per agent dir. A crash loop rewrites the file down to this many. */
2
+ export declare const PROCESS_CRASH_RECORD_LIMIT = 50;
3
+ /** How often a live process refreshes its marker, which bounds the error of a recorded uptime. */
4
+ export declare const PROCESS_MARKER_HEARTBEAT_MS = 60000;
5
+ export interface ProcessCrashPaths {
6
+ readonly dir: string;
7
+ readonly liveDir: string;
8
+ readonly recordsFile: string;
9
+ }
10
+ export declare function processCrashPaths(agentDir: string): ProcessCrashPaths;
11
+ /** What a live process says about itself; the crash record is derived from it. */
12
+ export interface ProcessLifetimeMarker {
13
+ readonly kind: string;
14
+ readonly pid: number;
15
+ readonly startedAt: number;
16
+ readonly bunVersion?: string;
17
+ readonly senpiVersion: string;
18
+ /** The branded product's version (omo's), present only when a brand names one. */
19
+ readonly productVersion?: string;
20
+ }
21
+ export interface ProcessCrashRecord {
22
+ /** ISO-8601 instant of the last heartbeat before the death: the latest moment it was known alive. */
23
+ readonly at: string;
24
+ /** Which kind of process died: the app mode (`interactive`, `print`, `json`). */
25
+ readonly kind: string;
26
+ /** A marker left behind proves the death but not its signal. */
27
+ readonly detection: "unclean_exit";
28
+ /** Start to last heartbeat, in milliseconds; short by at most one heartbeat interval. */
29
+ readonly uptimeMs: number;
30
+ readonly bunVersion?: string;
31
+ readonly senpiVersion: string;
32
+ readonly productVersion?: string;
33
+ }
34
+ /** Runtime identity every record carries; the product version only when a brand sets one. */
35
+ export declare function crashingRuntimeVersions(): {
36
+ bunVersion?: string;
37
+ senpiVersion: string;
38
+ productVersion?: string;
39
+ };
40
+ export interface RecordProcessLifetimeOptions {
41
+ readonly pid?: number;
42
+ readonly now?: () => number;
43
+ readonly isAlive?: (pid: number) => boolean;
44
+ readonly heartbeatMs?: number;
45
+ }
46
+ /**
47
+ * The launch seam: turn every dead peer's marker into a crash record, then, for a process no parent
48
+ * supervises, start this process's own marker. Returns the disposer of this process's marker.
49
+ */
50
+ export declare function recordProcessLifetime(agentDir: string, kind: string, options: RecordProcessLifetimeOptions & {
51
+ readonly supervised: boolean;
52
+ }): () => void;
53
+ /** Write this process's marker, keep it fresh, and remove it on every observable exit. */
54
+ export declare function installProcessLifetimeMarker(agentDir: string, kind: string, options?: RecordProcessLifetimeOptions): () => void;
55
+ /**
56
+ * Record one crash per marker whose process is gone. A marker is CLAIMED by renaming it before it is
57
+ * recorded, so two processes starting at once cannot both record the same death.
58
+ */
59
+ export declare function sweepDeadProcessMarkers(agentDir: string, options?: RecordProcessLifetimeOptions): number;
60
+ /** Every record currently held, oldest first. Unparseable lines are skipped, never thrown on. */
61
+ export declare function readProcessCrashRecords(agentDir: string): readonly ProcessCrashRecord[];
62
+ //# sourceMappingURL=process-crash-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-crash-record.d.ts","sourceRoot":"","sources":["../../src/core/process-crash-record.ts"],"names":[],"mappings":"AAmCA,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,kGAAkG;AAClG,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAMlD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAGrE;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAClC,qGAAqG;IACrG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,6FAA6F;AAC7F,wBAAgB,uBAAuB,IAAI;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAMhH;AAED,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,4BAA4B,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GACtE,MAAM,IAAI,CAGZ;AAED,0FAA0F;AAC1F,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,4BAAiC,GACxC,MAAM,IAAI,CA4CZ;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,MAAM,CAkC5G;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAkBvF"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Durable evidence that a senpi process nobody supervises DIED rather than exited.
3
+ *
4
+ * The shared RPC host has a supervisor that sees its child's exit signal and writes
5
+ * `rpc-host-daemon/<endpoint>/crashes.jsonl` (`modes/rpc/host-crash-record.ts`). An interactive or
6
+ * print process has no such parent: the omo launcher `execve`s into it on POSIX, so a native crash
7
+ * (a JSC heap corruption, a Bun panic) leaves nothing behind but an OS crash report.
8
+ *
9
+ * Such a process can still leave evidence of its own death by what it FAILS to do. At boot it writes
10
+ * a lifetime marker, `process-crashes/live/<pid>.json`, keeps the marker's mtime fresh as a
11
+ * heartbeat, and removes it on every exit JavaScript can observe - `process.exit`, a natural end, a
12
+ * handled or default-fatal signal (a plain `exit` listener plus `signal-exit`, which every senpi
13
+ * process already loads for `proper-lockfile`, so no new signal behaviour is installed). A native crash or SIGKILL runs none of
14
+ * that. The next senpi start finds the marker of a dead pid and turns it into one crash record in
15
+ * `process-crashes/crashes.jsonl`. The signal is unknowable from outside, so the record says
16
+ * `detection: "unclean_exit"` instead of guessing one.
17
+ *
18
+ * Every step is best-effort and never throws: crash evidence must not be able to delay or break the
19
+ * launch it only annotates.
20
+ */
21
+ import { appendFileSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, utimesSync, writeFileSync, } from "node:fs";
22
+ import { join } from "node:path";
23
+ import onExit from "signal-exit";
24
+ import { DISPLAY_VERSION, VERSION } from "../config.js";
25
+ /** Crash records kept per agent dir. A crash loop rewrites the file down to this many. */
26
+ export const PROCESS_CRASH_RECORD_LIMIT = 50;
27
+ /** How often a live process refreshes its marker, which bounds the error of a recorded uptime. */
28
+ export const PROCESS_MARKER_HEARTBEAT_MS = 60_000;
29
+ const FILE_MODE = 0o600;
30
+ const DIR_MODE = 0o700;
31
+ const MARKER_SUFFIX = ".json";
32
+ export function processCrashPaths(agentDir) {
33
+ const dir = join(agentDir, "process-crashes");
34
+ return { dir, liveDir: join(dir, "live"), recordsFile: join(dir, "crashes.jsonl") };
35
+ }
36
+ /** Runtime identity every record carries; the product version only when a brand sets one. */
37
+ export function crashingRuntimeVersions() {
38
+ return {
39
+ ...(process.versions.bun === undefined ? {} : { bunVersion: process.versions.bun }),
40
+ senpiVersion: VERSION,
41
+ ...(DISPLAY_VERSION === VERSION ? {} : { productVersion: DISPLAY_VERSION }),
42
+ };
43
+ }
44
+ /**
45
+ * The launch seam: turn every dead peer's marker into a crash record, then, for a process no parent
46
+ * supervises, start this process's own marker. Returns the disposer of this process's marker.
47
+ */
48
+ export function recordProcessLifetime(agentDir, kind, options) {
49
+ sweepDeadProcessMarkers(agentDir, options);
50
+ return options.supervised ? () => { } : installProcessLifetimeMarker(agentDir, kind, options);
51
+ }
52
+ /** Write this process's marker, keep it fresh, and remove it on every observable exit. */
53
+ export function installProcessLifetimeMarker(agentDir, kind, options = {}) {
54
+ const pid = options.pid ?? process.pid;
55
+ const now = options.now ?? Date.now;
56
+ const file = join(processCrashPaths(agentDir).liveDir, `${pid}${MARKER_SUFFIX}`);
57
+ const marker = {
58
+ kind,
59
+ pid,
60
+ startedAt: now(),
61
+ ...crashingRuntimeVersions(),
62
+ };
63
+ try {
64
+ mkdirSync(processCrashPaths(agentDir).liveDir, { recursive: true, mode: DIR_MODE });
65
+ writeFileSync(file, `${JSON.stringify(marker)}\n`, { mode: FILE_MODE });
66
+ }
67
+ catch {
68
+ return () => { };
69
+ }
70
+ const heartbeat = setInterval(() => {
71
+ try {
72
+ const at = new Date(now());
73
+ utimesSync(file, at, at);
74
+ }
75
+ catch {
76
+ // A missing or unwritable marker only costs the uptime precision of a later crash.
77
+ }
78
+ }, options.heartbeatMs ?? PROCESS_MARKER_HEARTBEAT_MS);
79
+ heartbeat.unref();
80
+ let disposed = false;
81
+ const dispose = () => {
82
+ if (disposed)
83
+ return;
84
+ disposed = true;
85
+ clearInterval(heartbeat);
86
+ process.off("exit", onProcessExit);
87
+ removeSignalHook();
88
+ rmSync(file, { force: true });
89
+ };
90
+ const onProcessExit = () => {
91
+ try {
92
+ dispose();
93
+ }
94
+ catch { }
95
+ };
96
+ // Bun emits a natural exit's `exit` event without the `process.emit` that signal-exit patches, so
97
+ // the plain listener covers every exit and signal-exit only covers the default-fatal signals.
98
+ process.on("exit", onProcessExit);
99
+ const removeSignalHook = onExit(onProcessExit);
100
+ return dispose;
101
+ }
102
+ /**
103
+ * Record one crash per marker whose process is gone. A marker is CLAIMED by renaming it before it is
104
+ * recorded, so two processes starting at once cannot both record the same death.
105
+ */
106
+ export function sweepDeadProcessMarkers(agentDir, options = {}) {
107
+ const paths = processCrashPaths(agentDir);
108
+ const selfPid = options.pid ?? process.pid;
109
+ const isAlive = options.isAlive ?? isProcessAlive;
110
+ let names;
111
+ try {
112
+ names = readdirSync(paths.liveDir);
113
+ }
114
+ catch {
115
+ return 0;
116
+ }
117
+ let recorded = 0;
118
+ for (const name of names) {
119
+ if (!name.endsWith(MARKER_SUFFIX))
120
+ continue;
121
+ const pid = Number(name.slice(0, -MARKER_SUFFIX.length));
122
+ if (!Number.isSafeInteger(pid) || pid <= 0 || pid === selfPid || isAlive(pid))
123
+ continue;
124
+ const claimed = join(paths.liveDir, `${name}.${selfPid}.sweep`);
125
+ try {
126
+ renameSync(join(paths.liveDir, name), claimed);
127
+ }
128
+ catch {
129
+ continue; // Another process claimed it first, or it was removed by its own exit.
130
+ }
131
+ try {
132
+ const record = crashRecordFromMarker(readFileSync(claimed, "utf8"), statSync(claimed).mtimeMs);
133
+ if (record !== undefined) {
134
+ appendProcessCrashRecord(paths, record);
135
+ recorded += 1;
136
+ }
137
+ }
138
+ catch {
139
+ // An unreadable marker is not evidence of anything; it is discarded below.
140
+ }
141
+ finally {
142
+ rmSync(claimed, { force: true });
143
+ }
144
+ }
145
+ return recorded;
146
+ }
147
+ /** Every record currently held, oldest first. Unparseable lines are skipped, never thrown on. */
148
+ export function readProcessCrashRecords(agentDir) {
149
+ let raw;
150
+ try {
151
+ raw = readFileSync(processCrashPaths(agentDir).recordsFile, "utf8");
152
+ }
153
+ catch {
154
+ return [];
155
+ }
156
+ const records = [];
157
+ for (const line of raw.split("\n")) {
158
+ if (line.trim() === "")
159
+ continue;
160
+ try {
161
+ const value = JSON.parse(line);
162
+ if (typeof value === "object" && value !== null && typeof Reflect.get(value, "at") === "string") {
163
+ records.push(value);
164
+ }
165
+ }
166
+ catch { }
167
+ }
168
+ return records;
169
+ }
170
+ /** Signal 0 probes existence; EPERM means the pid exists under another user, so it is alive. */
171
+ function isProcessAlive(pid) {
172
+ try {
173
+ process.kill(pid, 0);
174
+ return true;
175
+ }
176
+ catch (error) {
177
+ return error.code === "EPERM";
178
+ }
179
+ }
180
+ function crashRecordFromMarker(raw, lastHeartbeatMs) {
181
+ const value = JSON.parse(raw);
182
+ if (typeof value !== "object" || value === null)
183
+ return undefined;
184
+ const kind = Reflect.get(value, "kind");
185
+ const startedAt = Reflect.get(value, "startedAt");
186
+ const senpiVersion = Reflect.get(value, "senpiVersion");
187
+ const bunVersion = Reflect.get(value, "bunVersion");
188
+ const productVersion = Reflect.get(value, "productVersion");
189
+ if (typeof kind !== "string" || typeof startedAt !== "number" || typeof senpiVersion !== "string")
190
+ return undefined;
191
+ return {
192
+ at: new Date(lastHeartbeatMs).toISOString(),
193
+ kind,
194
+ detection: "unclean_exit",
195
+ uptimeMs: Math.max(0, Math.round(lastHeartbeatMs - startedAt)),
196
+ ...(typeof bunVersion === "string" ? { bunVersion } : {}),
197
+ senpiVersion,
198
+ ...(typeof productVersion === "string" ? { productVersion } : {}),
199
+ };
200
+ }
201
+ function appendProcessCrashRecord(paths, record) {
202
+ mkdirSync(paths.dir, { recursive: true, mode: DIR_MODE });
203
+ appendFileSync(paths.recordsFile, `${JSON.stringify(record)}\n`, { mode: FILE_MODE });
204
+ const records = readProcessCrashRecordsFrom(paths.recordsFile);
205
+ if (records.length <= PROCESS_CRASH_RECORD_LIMIT)
206
+ return;
207
+ const kept = records.slice(records.length - PROCESS_CRASH_RECORD_LIMIT);
208
+ writeFileSync(paths.recordsFile, `${kept.join("\n")}\n`, { mode: FILE_MODE });
209
+ }
210
+ /** Raw non-empty lines, so pruning keeps each surviving record byte-identical. */
211
+ function readProcessCrashRecordsFrom(file) {
212
+ return readFileSync(file, "utf8")
213
+ .split("\n")
214
+ .filter((line) => line.trim() !== "");
215
+ }
216
+ //# sourceMappingURL=process-crash-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-crash-record.js","sourceRoot":"","sources":["../../src/core/process-crash-record.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable evidence that a senpi process nobody supervises DIED rather than exited.\n *\n * The shared RPC host has a supervisor that sees its child's exit signal and writes\n * `rpc-host-daemon/<endpoint>/crashes.jsonl` (`modes/rpc/host-crash-record.ts`). An interactive or\n * print process has no such parent: the omo launcher `execve`s into it on POSIX, so a native crash\n * (a JSC heap corruption, a Bun panic) leaves nothing behind but an OS crash report.\n *\n * Such a process can still leave evidence of its own death by what it FAILS to do. At boot it writes\n * a lifetime marker, `process-crashes/live/<pid>.json`, keeps the marker's mtime fresh as a\n * heartbeat, and removes it on every exit JavaScript can observe - `process.exit`, a natural end, a\n * handled or default-fatal signal (a plain `exit` listener plus `signal-exit`, which every senpi\n * process already loads for `proper-lockfile`, so no new signal behaviour is installed). A native crash or SIGKILL runs none of\n * that. The next senpi start finds the marker of a dead pid and turns it into one crash record in\n * `process-crashes/crashes.jsonl`. The signal is unknowable from outside, so the record says\n * `detection: \"unclean_exit\"` instead of guessing one.\n *\n * Every step is best-effort and never throws: crash evidence must not be able to delay or break the\n * launch it only annotates.\n */\nimport {\n\tappendFileSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n\tutimesSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport onExit from \"signal-exit\";\nimport { DISPLAY_VERSION, VERSION } from \"../config.ts\";\n\n/** Crash records kept per agent dir. A crash loop rewrites the file down to this many. */\nexport const PROCESS_CRASH_RECORD_LIMIT = 50;\n\n/** How often a live process refreshes its marker, which bounds the error of a recorded uptime. */\nexport const PROCESS_MARKER_HEARTBEAT_MS = 60_000;\n\nconst FILE_MODE = 0o600;\nconst DIR_MODE = 0o700;\nconst MARKER_SUFFIX = \".json\";\n\nexport interface ProcessCrashPaths {\n\treadonly dir: string;\n\treadonly liveDir: string;\n\treadonly recordsFile: string;\n}\n\nexport function processCrashPaths(agentDir: string): ProcessCrashPaths {\n\tconst dir = join(agentDir, \"process-crashes\");\n\treturn { dir, liveDir: join(dir, \"live\"), recordsFile: join(dir, \"crashes.jsonl\") };\n}\n\n/** What a live process says about itself; the crash record is derived from it. */\nexport interface ProcessLifetimeMarker {\n\treadonly kind: string;\n\treadonly pid: number;\n\treadonly startedAt: number;\n\treadonly bunVersion?: string;\n\treadonly senpiVersion: string;\n\t/** The branded product's version (omo's), present only when a brand names one. */\n\treadonly productVersion?: string;\n}\n\nexport interface ProcessCrashRecord {\n\t/** ISO-8601 instant of the last heartbeat before the death: the latest moment it was known alive. */\n\treadonly at: string;\n\t/** Which kind of process died: the app mode (`interactive`, `print`, `json`). */\n\treadonly kind: string;\n\t/** A marker left behind proves the death but not its signal. */\n\treadonly detection: \"unclean_exit\";\n\t/** Start to last heartbeat, in milliseconds; short by at most one heartbeat interval. */\n\treadonly uptimeMs: number;\n\treadonly bunVersion?: string;\n\treadonly senpiVersion: string;\n\treadonly productVersion?: string;\n}\n\n/** Runtime identity every record carries; the product version only when a brand sets one. */\nexport function crashingRuntimeVersions(): { bunVersion?: string; senpiVersion: string; productVersion?: string } {\n\treturn {\n\t\t...(process.versions.bun === undefined ? {} : { bunVersion: process.versions.bun }),\n\t\tsenpiVersion: VERSION,\n\t\t...(DISPLAY_VERSION === VERSION ? {} : { productVersion: DISPLAY_VERSION }),\n\t};\n}\n\nexport interface RecordProcessLifetimeOptions {\n\treadonly pid?: number;\n\treadonly now?: () => number;\n\treadonly isAlive?: (pid: number) => boolean;\n\treadonly heartbeatMs?: number;\n}\n\n/**\n * The launch seam: turn every dead peer's marker into a crash record, then, for a process no parent\n * supervises, start this process's own marker. Returns the disposer of this process's marker.\n */\nexport function recordProcessLifetime(\n\tagentDir: string,\n\tkind: string,\n\toptions: RecordProcessLifetimeOptions & { readonly supervised: boolean },\n): () => void {\n\tsweepDeadProcessMarkers(agentDir, options);\n\treturn options.supervised ? () => {} : installProcessLifetimeMarker(agentDir, kind, options);\n}\n\n/** Write this process's marker, keep it fresh, and remove it on every observable exit. */\nexport function installProcessLifetimeMarker(\n\tagentDir: string,\n\tkind: string,\n\toptions: RecordProcessLifetimeOptions = {},\n): () => void {\n\tconst pid = options.pid ?? process.pid;\n\tconst now = options.now ?? Date.now;\n\tconst file = join(processCrashPaths(agentDir).liveDir, `${pid}${MARKER_SUFFIX}`);\n\tconst marker: ProcessLifetimeMarker = {\n\t\tkind,\n\t\tpid,\n\t\tstartedAt: now(),\n\t\t...crashingRuntimeVersions(),\n\t};\n\ttry {\n\t\tmkdirSync(processCrashPaths(agentDir).liveDir, { recursive: true, mode: DIR_MODE });\n\t\twriteFileSync(file, `${JSON.stringify(marker)}\\n`, { mode: FILE_MODE });\n\t} catch {\n\t\treturn () => {};\n\t}\n\tconst heartbeat = setInterval(() => {\n\t\ttry {\n\t\t\tconst at = new Date(now());\n\t\t\tutimesSync(file, at, at);\n\t\t} catch {\n\t\t\t// A missing or unwritable marker only costs the uptime precision of a later crash.\n\t\t}\n\t}, options.heartbeatMs ?? PROCESS_MARKER_HEARTBEAT_MS);\n\theartbeat.unref();\n\tlet disposed = false;\n\tconst dispose = (): void => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tclearInterval(heartbeat);\n\t\tprocess.off(\"exit\", onProcessExit);\n\t\tremoveSignalHook();\n\t\trmSync(file, { force: true });\n\t};\n\tconst onProcessExit = (): void => {\n\t\ttry {\n\t\t\tdispose();\n\t\t} catch {}\n\t};\n\t// Bun emits a natural exit's `exit` event without the `process.emit` that signal-exit patches, so\n\t// the plain listener covers every exit and signal-exit only covers the default-fatal signals.\n\tprocess.on(\"exit\", onProcessExit);\n\tconst removeSignalHook = onExit(onProcessExit);\n\treturn dispose;\n}\n\n/**\n * Record one crash per marker whose process is gone. A marker is CLAIMED by renaming it before it is\n * recorded, so two processes starting at once cannot both record the same death.\n */\nexport function sweepDeadProcessMarkers(agentDir: string, options: RecordProcessLifetimeOptions = {}): number {\n\tconst paths = processCrashPaths(agentDir);\n\tconst selfPid = options.pid ?? process.pid;\n\tconst isAlive = options.isAlive ?? isProcessAlive;\n\tlet names: string[];\n\ttry {\n\t\tnames = readdirSync(paths.liveDir);\n\t} catch {\n\t\treturn 0;\n\t}\n\tlet recorded = 0;\n\tfor (const name of names) {\n\t\tif (!name.endsWith(MARKER_SUFFIX)) continue;\n\t\tconst pid = Number(name.slice(0, -MARKER_SUFFIX.length));\n\t\tif (!Number.isSafeInteger(pid) || pid <= 0 || pid === selfPid || isAlive(pid)) continue;\n\t\tconst claimed = join(paths.liveDir, `${name}.${selfPid}.sweep`);\n\t\ttry {\n\t\t\trenameSync(join(paths.liveDir, name), claimed);\n\t\t} catch {\n\t\t\tcontinue; // Another process claimed it first, or it was removed by its own exit.\n\t\t}\n\t\ttry {\n\t\t\tconst record = crashRecordFromMarker(readFileSync(claimed, \"utf8\"), statSync(claimed).mtimeMs);\n\t\t\tif (record !== undefined) {\n\t\t\t\tappendProcessCrashRecord(paths, record);\n\t\t\t\trecorded += 1;\n\t\t\t}\n\t\t} catch {\n\t\t\t// An unreadable marker is not evidence of anything; it is discarded below.\n\t\t} finally {\n\t\t\trmSync(claimed, { force: true });\n\t\t}\n\t}\n\treturn recorded;\n}\n\n/** Every record currently held, oldest first. Unparseable lines are skipped, never thrown on. */\nexport function readProcessCrashRecords(agentDir: string): readonly ProcessCrashRecord[] {\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(processCrashPaths(agentDir).recordsFile, \"utf8\");\n\t} catch {\n\t\treturn [];\n\t}\n\tconst records: ProcessCrashRecord[] = [];\n\tfor (const line of raw.split(\"\\n\")) {\n\t\tif (line.trim() === \"\") continue;\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(line);\n\t\t\tif (typeof value === \"object\" && value !== null && typeof Reflect.get(value, \"at\") === \"string\") {\n\t\t\t\trecords.push(value as ProcessCrashRecord);\n\t\t\t}\n\t\t} catch {}\n\t}\n\treturn records;\n}\n\n/** Signal 0 probes existence; EPERM means the pid exists under another user, so it is alive. */\nfunction isProcessAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\nfunction crashRecordFromMarker(raw: string, lastHeartbeatMs: number): ProcessCrashRecord | undefined {\n\tconst value: unknown = JSON.parse(raw);\n\tif (typeof value !== \"object\" || value === null) return undefined;\n\tconst kind: unknown = Reflect.get(value, \"kind\");\n\tconst startedAt: unknown = Reflect.get(value, \"startedAt\");\n\tconst senpiVersion: unknown = Reflect.get(value, \"senpiVersion\");\n\tconst bunVersion: unknown = Reflect.get(value, \"bunVersion\");\n\tconst productVersion: unknown = Reflect.get(value, \"productVersion\");\n\tif (typeof kind !== \"string\" || typeof startedAt !== \"number\" || typeof senpiVersion !== \"string\") return undefined;\n\treturn {\n\t\tat: new Date(lastHeartbeatMs).toISOString(),\n\t\tkind,\n\t\tdetection: \"unclean_exit\",\n\t\tuptimeMs: Math.max(0, Math.round(lastHeartbeatMs - startedAt)),\n\t\t...(typeof bunVersion === \"string\" ? { bunVersion } : {}),\n\t\tsenpiVersion,\n\t\t...(typeof productVersion === \"string\" ? { productVersion } : {}),\n\t};\n}\n\nfunction appendProcessCrashRecord(paths: ProcessCrashPaths, record: ProcessCrashRecord): void {\n\tmkdirSync(paths.dir, { recursive: true, mode: DIR_MODE });\n\tappendFileSync(paths.recordsFile, `${JSON.stringify(record)}\\n`, { mode: FILE_MODE });\n\tconst records = readProcessCrashRecordsFrom(paths.recordsFile);\n\tif (records.length <= PROCESS_CRASH_RECORD_LIMIT) return;\n\tconst kept = records.slice(records.length - PROCESS_CRASH_RECORD_LIMIT);\n\twriteFileSync(paths.recordsFile, `${kept.join(\"\\n\")}\\n`, { mode: FILE_MODE });\n}\n\n/** Raw non-empty lines, so pruning keeps each surviving record byte-identical. */\nfunction readProcessCrashRecordsFrom(file: string): string[] {\n\treturn readFileSync(file, \"utf8\")\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.trim() !== \"\");\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { type RepositoryIdentity } from "./repository-identity.ts";
2
+ export interface ResumeTargetChoice {
3
+ readonly sessionPath: string;
4
+ readonly cwd: string;
5
+ readonly sessionDir?: string;
6
+ readonly confirm: (sessionCwd: string) => Promise<boolean>;
7
+ readonly readIdentity?: (dir: string) => Promise<RepositoryIdentity | undefined>;
8
+ }
9
+ export interface ResumeTarget {
10
+ readonly path: string;
11
+ readonly rebound: boolean;
12
+ }
13
+ /**
14
+ * Where a session picked for resuming should be opened from. A session of the current repository
15
+ * recorded at another path is offered the in-place rebind; declining, another repository, or the
16
+ * current directory's own session open the picked file unchanged. Rebind failures propagate
17
+ * (`SessionHeldError` names the process still holding the session).
18
+ */
19
+ export declare function resolveResumeTarget(choice: ResumeTargetChoice): Promise<ResumeTarget>;
20
+ //# sourceMappingURL=resume-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-target.d.ts","sourceRoot":"","sources":["../../src/core/resume-target.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAA0B,MAAM,0BAA0B,CAAC;AAG3F,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AAWD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAY3F"}
@@ -0,0 +1,30 @@
1
+ import { resolvePath } from "../utils/paths.js";
2
+ import { readRepositoryIdentity } from "./repository-identity.js";
3
+ import { classifySessionRepository, readSessionCwd, rebindSessionFile } from "./session-rebind.js";
4
+ function sessionCwdOf(sessionPath) {
5
+ try {
6
+ return readSessionCwd(sessionPath);
7
+ }
8
+ catch (error) {
9
+ if (error instanceof Error)
10
+ return undefined;
11
+ throw error;
12
+ }
13
+ }
14
+ /**
15
+ * Where a session picked for resuming should be opened from. A session of the current repository
16
+ * recorded at another path is offered the in-place rebind; declining, another repository, or the
17
+ * current directory's own session open the picked file unchanged. Rebind failures propagate
18
+ * (`SessionHeldError` names the process still holding the session).
19
+ */
20
+ export async function resolveResumeTarget(choice) {
21
+ const unchanged = { path: choice.sessionPath, rebound: false };
22
+ const sessionCwd = sessionCwdOf(choice.sessionPath);
23
+ if (sessionCwd === undefined || resolvePath(sessionCwd) === resolvePath(choice.cwd))
24
+ return unchanged;
25
+ const match = await classifySessionRepository(choice.sessionPath, sessionCwd, choice.cwd, choice.readIdentity ?? readRepositoryIdentity);
26
+ if (match !== "same" || !(await choice.confirm(sessionCwd)))
27
+ return unchanged;
28
+ return { path: await rebindSessionFile(choice.sessionPath, choice.cwd, choice.sessionDir), rebound: true };
29
+ }
30
+ //# sourceMappingURL=resume-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-target.js","sourceRoot":"","sources":["../../src/core/resume-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,yBAAyB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAenG,SAAS,YAAY,CAAC,WAAmB;IACxC,IAAI,CAAC;QACJ,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,KAAK,YAAY,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7C,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAA0B;IACnE,MAAM,SAAS,GAAiB,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7E,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,CAAC,UAAU,CAAC,KAAK,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACtG,MAAM,KAAK,GAAG,MAAM,yBAAyB,CAC5C,MAAM,CAAC,WAAW,EAClB,UAAU,EACV,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,YAAY,IAAI,sBAAsB,CAC7C,CAAC;IACF,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC5G,CAAC","sourcesContent":["import { resolvePath } from \"../utils/paths.ts\";\nimport { type RepositoryIdentity, readRepositoryIdentity } from \"./repository-identity.ts\";\nimport { classifySessionRepository, readSessionCwd, rebindSessionFile } from \"./session-rebind.ts\";\n\nexport interface ResumeTargetChoice {\n\treadonly sessionPath: string;\n\treadonly cwd: string;\n\treadonly sessionDir?: string;\n\treadonly confirm: (sessionCwd: string) => Promise<boolean>;\n\treadonly readIdentity?: (dir: string) => Promise<RepositoryIdentity | undefined>;\n}\n\nexport interface ResumeTarget {\n\treadonly path: string;\n\treadonly rebound: boolean;\n}\n\nfunction sessionCwdOf(sessionPath: string): string | undefined {\n\ttry {\n\t\treturn readSessionCwd(sessionPath);\n\t} catch (error) {\n\t\tif (error instanceof Error) return undefined;\n\t\tthrow error;\n\t}\n}\n\n/**\n * Where a session picked for resuming should be opened from. A session of the current repository\n * recorded at another path is offered the in-place rebind; declining, another repository, or the\n * current directory's own session open the picked file unchanged. Rebind failures propagate\n * (`SessionHeldError` names the process still holding the session).\n */\nexport async function resolveResumeTarget(choice: ResumeTargetChoice): Promise<ResumeTarget> {\n\tconst unchanged: ResumeTarget = { path: choice.sessionPath, rebound: false };\n\tconst sessionCwd = sessionCwdOf(choice.sessionPath);\n\tif (sessionCwd === undefined || resolvePath(sessionCwd) === resolvePath(choice.cwd)) return unchanged;\n\tconst match = await classifySessionRepository(\n\t\tchoice.sessionPath,\n\t\tsessionCwd,\n\t\tchoice.cwd,\n\t\tchoice.readIdentity ?? readRepositoryIdentity,\n\t);\n\tif (match !== \"same\" || !(await choice.confirm(sessionCwd))) return unchanged;\n\treturn { path: await rebindSessionFile(choice.sessionPath, choice.cwd, choice.sessionDir), rebound: true };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-discovery.d.ts","sourceRoot":"","sources":["../../src/core/session-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGhG,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAU1E;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmBjH;AA2CD,yFAAyF;AACzF,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,IAAI,EACpB,KAAK,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,EAAE,CAAC,CAOxB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAC1C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,IAAI,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC,CAKxB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,mBAAmB,EAChC,cAAc,SAAI,EAClB,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CAgBxB"}
1
+ {"version":3,"file":"session-discovery.d.ts","sourceRoot":"","sources":["../../src/core/session-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGhG,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAU1E;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoBjH;AA2CD,yFAAyF;AACzF,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,IAAI,EACpB,KAAK,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,EAAE,CAAC,CAOxB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAC1C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,IAAI,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC,CAKxB;AAED,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,mBAAmB,EAChC,cAAc,SAAI,EAClB,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CAgBxB"}
@@ -35,6 +35,7 @@ export async function buildSessionInfo(filePath, store) {
35
35
  messageCount: summary.messageCount,
36
36
  firstMessage: summary.firstUserMessage || "(no messages)",
37
37
  allMessagesText: summary.allMessagesText,
38
+ ...(summary.repositoryIdentity ? { repositoryIdentity: summary.repositoryIdentity } : {}),
38
39
  };
39
40
  }
40
41
  async function buildSessionInfosWithConcurrency(files, onLoaded, store) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-discovery.js","sourceRoot":"","sources":["../../src/core/session-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"fs\";\nimport { readdir } from \"fs/promises\";\nimport { join } from \"path\";\nimport type { SessionHeader, SessionInfo } from \"./session-manager.ts\";\nimport { readCachedSessionSummary, type SessionSummaryStore } from \"./session-summary-cache.ts\";\nimport { SessionSummaryIndex } from \"./session-summary-index.ts\";\n\nexport type SessionListProgress = (loaded: number, total: number) => void;\n\nconst MAX_CONCURRENT_SESSION_INFO_LOADS = 10;\n\nfunction resolveModified(activityTime: number | undefined, header: SessionHeader, mtime: Date): Date {\n\tif (typeof activityTime === \"number\" && activityTime > 0) return new Date(activityTime);\n\tconst headerTime = typeof header.timestamp === \"string\" ? Date.parse(header.timestamp) : Number.NaN;\n\treturn Number.isNaN(headerTime) ? mtime : new Date(headerTime);\n}\n\n/**\n * Build one picker row from a session file's exact summary.\n *\n * The summary is derived from every record in the file, so a row's name, first\n * message, message count, and activity time are exact regardless of where those\n * records sit. Repeat listings of an unchanged file are served from the\n * stat-keyed summary cache (or `store` on a cache miss) and stream nothing.\n */\nexport async function buildSessionInfo(filePath: string, store?: SessionSummaryStore): Promise<SessionInfo | null> {\n\tconst cached = await readCachedSessionSummary(filePath, store);\n\tif (!cached) return null;\n\n\tconst { summary, mtime } = cached;\n\tconst header = summary.header;\n\n\treturn {\n\t\tpath: filePath,\n\t\tid: header.id,\n\t\tcwd: typeof header.cwd === \"string\" ? header.cwd : \"\",\n\t\tname: summary.name,\n\t\tparentSessionPath: header.parentSession,\n\t\tcreated: new Date(header.timestamp),\n\t\tmodified: resolveModified(summary.lastActivityTime, header, mtime),\n\t\tmessageCount: summary.messageCount,\n\t\tfirstMessage: summary.firstUserMessage || \"(no messages)\",\n\t\tallMessagesText: summary.allMessagesText,\n\t};\n}\n\nasync function buildSessionInfosWithConcurrency(\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n\tstore: SessionSummaryStore | undefined,\n): Promise<(SessionInfo | null)[]> {\n\tconst results: (SessionInfo | null)[] = new Array(files.length).fill(null);\n\tconst inFlight = new Set<Promise<void>>();\n\tlet nextIndex = 0;\n\n\tconst startNext = (): void => {\n\t\tconst index = nextIndex++;\n\t\tconst file = files[index];\n\t\tif (!file) return;\n\n\t\tlet task: Promise<void>;\n\t\ttask = buildSessionInfo(file, store)\n\t\t\t.then((info) => {\n\t\t\t\tresults[index] = info;\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tresults[index] = null;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tinFlight.delete(task);\n\t\t\t\tonLoaded();\n\t\t\t});\n\t\tinFlight.add(task);\n\t};\n\n\twhile (nextIndex < files.length || inFlight.size > 0) {\n\t\twhile (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) {\n\t\t\tstartNext();\n\t\t}\n\t\tif (inFlight.size > 0) {\n\t\t\tawait Promise.race(inFlight);\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/** Build picker rows for an explicit file list, dropping files that are not sessions. */\nexport async function listSessionInfos(\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n\tstore?: SessionSummaryStore,\n): Promise<SessionInfo[]> {\n\tconst results = await buildSessionInfosWithConcurrency(files, onLoaded, store);\n\tconst sessions: SessionInfo[] = [];\n\tfor (const info of results) {\n\t\tif (info) sessions.push(info);\n\t}\n\treturn sessions;\n}\n\n/**\n * Build picker rows for session files that all live in `dir`, served through\n * and then persisted into that directory's summary index.\n */\nexport async function listSessionFilesInDir(\n\tdir: string,\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n): Promise<SessionInfo[]> {\n\tconst index = new SessionSummaryIndex(dir);\n\tconst sessions = await listSessionInfos(files, onLoaded, index);\n\tawait index.persist(files);\n\treturn sessions;\n}\n\n/** Build picker rows for every `.jsonl` file in one session directory. */\nexport async function listSessionsFromDir(\n\tdir: string,\n\tonProgress?: SessionListProgress,\n\tprogressOffset = 0,\n\tprogressTotal?: number,\n): Promise<SessionInfo[]> {\n\tif (!existsSync(dir)) return [];\n\n\ttry {\n\t\tconst dirEntries = await readdir(dir);\n\t\tconst files = dirEntries.filter((name) => name.endsWith(\".jsonl\")).map((name) => join(dir, name));\n\t\tconst total = progressTotal ?? files.length;\n\t\tlet loaded = 0;\n\t\treturn await listSessionFilesInDir(dir, files, () => {\n\t\t\tloaded++;\n\t\t\tonProgress?.(progressOffset + loaded, total);\n\t\t});\n\t} catch {\n\t\t// A directory that cannot be read contributes no picker rows.\n\t\treturn [];\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-discovery.js","sourceRoot":"","sources":["../../src/core/session-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"fs\";\nimport { readdir } from \"fs/promises\";\nimport { join } from \"path\";\nimport type { SessionHeader, SessionInfo } from \"./session-manager.ts\";\nimport { readCachedSessionSummary, type SessionSummaryStore } from \"./session-summary-cache.ts\";\nimport { SessionSummaryIndex } from \"./session-summary-index.ts\";\n\nexport type SessionListProgress = (loaded: number, total: number) => void;\n\nconst MAX_CONCURRENT_SESSION_INFO_LOADS = 10;\n\nfunction resolveModified(activityTime: number | undefined, header: SessionHeader, mtime: Date): Date {\n\tif (typeof activityTime === \"number\" && activityTime > 0) return new Date(activityTime);\n\tconst headerTime = typeof header.timestamp === \"string\" ? Date.parse(header.timestamp) : Number.NaN;\n\treturn Number.isNaN(headerTime) ? mtime : new Date(headerTime);\n}\n\n/**\n * Build one picker row from a session file's exact summary.\n *\n * The summary is derived from every record in the file, so a row's name, first\n * message, message count, and activity time are exact regardless of where those\n * records sit. Repeat listings of an unchanged file are served from the\n * stat-keyed summary cache (or `store` on a cache miss) and stream nothing.\n */\nexport async function buildSessionInfo(filePath: string, store?: SessionSummaryStore): Promise<SessionInfo | null> {\n\tconst cached = await readCachedSessionSummary(filePath, store);\n\tif (!cached) return null;\n\n\tconst { summary, mtime } = cached;\n\tconst header = summary.header;\n\n\treturn {\n\t\tpath: filePath,\n\t\tid: header.id,\n\t\tcwd: typeof header.cwd === \"string\" ? header.cwd : \"\",\n\t\tname: summary.name,\n\t\tparentSessionPath: header.parentSession,\n\t\tcreated: new Date(header.timestamp),\n\t\tmodified: resolveModified(summary.lastActivityTime, header, mtime),\n\t\tmessageCount: summary.messageCount,\n\t\tfirstMessage: summary.firstUserMessage || \"(no messages)\",\n\t\tallMessagesText: summary.allMessagesText,\n\t\t...(summary.repositoryIdentity ? { repositoryIdentity: summary.repositoryIdentity } : {}),\n\t};\n}\n\nasync function buildSessionInfosWithConcurrency(\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n\tstore: SessionSummaryStore | undefined,\n): Promise<(SessionInfo | null)[]> {\n\tconst results: (SessionInfo | null)[] = new Array(files.length).fill(null);\n\tconst inFlight = new Set<Promise<void>>();\n\tlet nextIndex = 0;\n\n\tconst startNext = (): void => {\n\t\tconst index = nextIndex++;\n\t\tconst file = files[index];\n\t\tif (!file) return;\n\n\t\tlet task: Promise<void>;\n\t\ttask = buildSessionInfo(file, store)\n\t\t\t.then((info) => {\n\t\t\t\tresults[index] = info;\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tresults[index] = null;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tinFlight.delete(task);\n\t\t\t\tonLoaded();\n\t\t\t});\n\t\tinFlight.add(task);\n\t};\n\n\twhile (nextIndex < files.length || inFlight.size > 0) {\n\t\twhile (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) {\n\t\t\tstartNext();\n\t\t}\n\t\tif (inFlight.size > 0) {\n\t\t\tawait Promise.race(inFlight);\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/** Build picker rows for an explicit file list, dropping files that are not sessions. */\nexport async function listSessionInfos(\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n\tstore?: SessionSummaryStore,\n): Promise<SessionInfo[]> {\n\tconst results = await buildSessionInfosWithConcurrency(files, onLoaded, store);\n\tconst sessions: SessionInfo[] = [];\n\tfor (const info of results) {\n\t\tif (info) sessions.push(info);\n\t}\n\treturn sessions;\n}\n\n/**\n * Build picker rows for session files that all live in `dir`, served through\n * and then persisted into that directory's summary index.\n */\nexport async function listSessionFilesInDir(\n\tdir: string,\n\tfiles: readonly string[],\n\tonLoaded: () => void,\n): Promise<SessionInfo[]> {\n\tconst index = new SessionSummaryIndex(dir);\n\tconst sessions = await listSessionInfos(files, onLoaded, index);\n\tawait index.persist(files);\n\treturn sessions;\n}\n\n/** Build picker rows for every `.jsonl` file in one session directory. */\nexport async function listSessionsFromDir(\n\tdir: string,\n\tonProgress?: SessionListProgress,\n\tprogressOffset = 0,\n\tprogressTotal?: number,\n): Promise<SessionInfo[]> {\n\tif (!existsSync(dir)) return [];\n\n\ttry {\n\t\tconst dirEntries = await readdir(dir);\n\t\tconst files = dirEntries.filter((name) => name.endsWith(\".jsonl\")).map((name) => join(dir, name));\n\t\tconst total = progressTotal ?? files.length;\n\t\tlet loaded = 0;\n\t\treturn await listSessionFilesInDir(dir, files, () => {\n\t\t\tloaded++;\n\t\t\tonProgress?.(progressOffset + loaded, total);\n\t\t});\n\t} catch {\n\t\t// A directory that cannot be read contributes no picker rows.\n\t\treturn [];\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ export interface SessionHolder {
2
+ readonly pid: number;
3
+ readonly cwd: string | undefined;
4
+ }
5
+ export declare class SessionHeldError extends Error {
6
+ readonly code: "ESESSIONHELD";
7
+ readonly sessionFile: string;
8
+ readonly holders: readonly SessionHolder[];
9
+ constructor(sessionFile: string, holders: readonly SessionHolder[]);
10
+ }
11
+ export declare class SessionMovingError extends Error {
12
+ readonly code: "ESESSIONMOVING";
13
+ readonly moverPid: number | undefined;
14
+ constructor(sessionFile: string, moverPid: number | undefined);
15
+ }
16
+ export declare class SessionMovedError extends Error {
17
+ readonly code: "ESESSIONMOVED";
18
+ constructor(sessionFile: string);
19
+ }
20
+ //# sourceMappingURL=session-hold-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-hold-errors.d.ts","sourceRoot":"","sources":["../../src/core/session-hold-errors.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAE3C,YAAY,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,aAAa,EAAE,EAMjE;CACD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,YAAY,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAK5D;CACD;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAG,eAAe,CAAU;IAEzC,YAAY,WAAW,EAAE,MAAM,EAG9B;CACD"}
@@ -0,0 +1,28 @@
1
+ import { APP_NAME } from "../config.js";
2
+ export class SessionHeldError extends Error {
3
+ constructor(sessionFile, holders) {
4
+ const who = holders.map((h) => (h.cwd ? `pid ${h.pid} in ${h.cwd}` : `pid ${h.pid}`)).join("; ");
5
+ super(`This session is open in another ${APP_NAME} process (${who}). Quit it there, then try again.`);
6
+ this.code = "ESESSIONHELD";
7
+ this.name = "SessionHeldError";
8
+ this.sessionFile = sessionFile;
9
+ this.holders = holders;
10
+ }
11
+ }
12
+ export class SessionMovingError extends Error {
13
+ constructor(sessionFile, moverPid) {
14
+ const who = moverPid === undefined ? "another process" : `another process (pid ${moverPid})`;
15
+ super(`Session ${sessionFile} is being moved by ${who}. Try again in a moment.`);
16
+ this.code = "ESESSIONMOVING";
17
+ this.name = "SessionMovingError";
18
+ this.moverPid = moverPid;
19
+ }
20
+ }
21
+ export class SessionMovedError extends Error {
22
+ constructor(sessionFile) {
23
+ super(`Session ${sessionFile} no longer exists: another process moved it. Resume it from its new location.`);
24
+ this.code = "ESESSIONMOVED";
25
+ this.name = "SessionMovedError";
26
+ }
27
+ }
28
+ //# sourceMappingURL=session-hold-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-hold-errors.js","sourceRoot":"","sources":["../../src/core/session-hold-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAOxC,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAK1C,YAAY,WAAmB,EAAE,OAAiC;QACjE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjG,KAAK,CAAC,mCAAmC,QAAQ,aAAa,GAAG,mCAAmC,CAAC,CAAC;QAN9F,SAAI,GAAG,cAAuB,CAAC;QAOvC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAI5C,YAAY,WAAmB,EAAE,QAA4B;QAC5D,MAAM,GAAG,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,wBAAwB,QAAQ,GAAG,CAAC;QAC7F,KAAK,CAAC,WAAW,WAAW,sBAAsB,GAAG,0BAA0B,CAAC,CAAC;QALzE,SAAI,GAAG,gBAAyB,CAAC;QAMzC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAG3C,YAAY,WAAmB;QAC9B,KAAK,CAAC,WAAW,WAAW,+EAA+E,CAAC,CAAC;QAHrG,SAAI,GAAG,eAAwB,CAAC;QAIxC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IACjC,CAAC;CACD","sourcesContent":["import { APP_NAME } from \"../config.ts\";\n\nexport interface SessionHolder {\n\treadonly pid: number;\n\treadonly cwd: string | undefined;\n}\n\nexport class SessionHeldError extends Error {\n\treadonly code = \"ESESSIONHELD\" as const;\n\treadonly sessionFile: string;\n\treadonly holders: readonly SessionHolder[];\n\n\tconstructor(sessionFile: string, holders: readonly SessionHolder[]) {\n\t\tconst who = holders.map((h) => (h.cwd ? `pid ${h.pid} in ${h.cwd}` : `pid ${h.pid}`)).join(\"; \");\n\t\tsuper(`This session is open in another ${APP_NAME} process (${who}). Quit it there, then try again.`);\n\t\tthis.name = \"SessionHeldError\";\n\t\tthis.sessionFile = sessionFile;\n\t\tthis.holders = holders;\n\t}\n}\n\nexport class SessionMovingError extends Error {\n\treadonly code = \"ESESSIONMOVING\" as const;\n\treadonly moverPid: number | undefined;\n\n\tconstructor(sessionFile: string, moverPid: number | undefined) {\n\t\tconst who = moverPid === undefined ? \"another process\" : `another process (pid ${moverPid})`;\n\t\tsuper(`Session ${sessionFile} is being moved by ${who}. Try again in a moment.`);\n\t\tthis.name = \"SessionMovingError\";\n\t\tthis.moverPid = moverPid;\n\t}\n}\n\nexport class SessionMovedError extends Error {\n\treadonly code = \"ESESSIONMOVED\" as const;\n\n\tconstructor(sessionFile: string) {\n\t\tsuper(`Session ${sessionFile} no longer exists: another process moved it. Resume it from its new location.`);\n\t\tthis.name = \"SessionMovedError\";\n\t}\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { SessionHeldError, type SessionHolder, SessionMovedError, SessionMovingError } from "./session-hold-errors.ts";
2
+ export { SessionHeldError, type SessionHolder, SessionMovedError, SessionMovingError };
3
+ export interface SessionHold {
4
+ release(): void;
5
+ }
6
+ /**
7
+ * Publishes this process as a holder of `sessionFile` until `release()`. Throws `SessionMovingError`
8
+ * while another process is moving the session, and `SessionMovedError` when a session that was read
9
+ * from disk (`expectExisting`) is gone, so nothing is ever appended to a path the session left.
10
+ */
11
+ export declare function holdSessionFile(sessionFile: string, sessionId: string, options: {
12
+ readonly cwd?: string;
13
+ readonly expectExisting: boolean;
14
+ }): SessionHold;
15
+ /**
16
+ * Runs `move` holding the session's move lock, after proving no live process holds the session.
17
+ * Waits up to `waitMs` for a concurrent move to finish, then fails with `SessionMovingError`.
18
+ */
19
+ export declare function withSessionMoveLock<T>(sessionFile: string, sessionId: string, move: () => T | Promise<T>, options?: {
20
+ readonly waitMs?: number;
21
+ }): Promise<T>;
22
+ //# sourceMappingURL=session-holders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-holders.d.ts","sourceRoot":"","sources":["../../src/core/session-holders.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGvH,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC;AAoBvF,MAAM,WAAW,WAAW;IAC3B,OAAO,IAAI,IAAI,CAAC;CAChB;AAqFD;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAClE,WAAW,CAgCb;AAyDD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAC1C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC1B,OAAO,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,OAAO,CAAC,CAAC,CAAC,CAYZ"}