@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
@@ -12,6 +12,12 @@ export declare function readSessionCwd(sessionFile: string): string | undefined;
12
12
  * header at `targetCwd`. The id, file name, every entry after the header, and the session's extension
13
13
  * sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted
14
14
  * rebind leaves the session readable in at least one place. Returns the session file's new path.
15
+ *
16
+ * The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while
17
+ * another live process has the session open. A rebind that finds the same move already done by a
18
+ * concurrent process returns that target.
15
19
  */
16
- export declare function rebindSessionFile(sourcePath: string, targetCwd: string, sessionDir?: string): string;
20
+ export declare function rebindSessionFile(sourcePath: string, targetCwd: string, sessionDir?: string, options?: {
21
+ readonly moveLockWaitMs?: number;
22
+ }): Promise<string>;
17
23
  //# sourceMappingURL=session-rebind.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-rebind.d.ts","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,MAAM,0BAA0B,CAAC;AASlC,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC9C,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAA0B,GACrF,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBpG"}
1
+ {"version":3,"file":"session-rebind.d.ts","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,MAAM,0BAA0B,CAAC;AAUlC,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC9C,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAA0B,GACrF,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,OAAO,CAAC,MAAM,CAAC,CAUjB"}
@@ -2,6 +2,7 @@ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, r
2
2
  import { basename, dirname, join } from "node:path";
3
3
  import { normalizePath, resolvePath } from "../utils/paths.js";
4
4
  import { compareRepositoryIdentities, parseRepositoryIdentity, REPOSITORY_IDENTITY_ENTRY_TYPE, readRepositoryIdentity, } from "./repository-identity.js";
5
+ import { withSessionMoveLock } from "./session-holders.js";
5
6
  import { getDefaultSessionDir, loadEntriesFromFile } from "./session-manager.js";
6
7
  import { encodedSessionId } from "./session-sidecar-store.js";
7
8
  export function readRecordedRepositoryIdentity(sessionFile) {
@@ -34,24 +35,39 @@ export function readSessionCwd(sessionFile) {
34
35
  * header at `targetCwd`. The id, file name, every entry after the header, and the session's extension
35
36
  * sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted
36
37
  * rebind leaves the session readable in at least one place. Returns the session file's new path.
38
+ *
39
+ * The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while
40
+ * another live process has the session open. A rebind that finds the same move already done by a
41
+ * concurrent process returns that target.
37
42
  */
38
- export function rebindSessionFile(sourcePath, targetCwd, sessionDir) {
43
+ export async function rebindSessionFile(sourcePath, targetCwd, sessionDir, options = {}) {
39
44
  const source = resolvePath(sourcePath);
40
45
  const cwd = resolvePath(targetCwd);
41
- const { header, rest } = readHeaderLine(source);
42
46
  const targetDir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
43
47
  const target = join(targetDir, basename(source));
48
+ if (target !== source && !existsSync(source) && existsSync(target))
49
+ return target;
50
+ const { header } = readHeaderLine(source);
51
+ return withSessionMoveLock(source, header.id, () => moveSessionFile(source, target, cwd), {
52
+ ...(options.moveLockWaitMs === undefined ? {} : { waitMs: options.moveLockWaitMs }),
53
+ });
54
+ }
55
+ function moveSessionFile(source, target, cwd) {
56
+ if (target !== source && !existsSync(source) && existsSync(target))
57
+ return target;
58
+ const { header, rest } = readHeaderLine(source);
44
59
  if (target !== source && existsSync(target)) {
45
60
  throw new Error(`Cannot rebind session: ${target} already exists`);
46
61
  }
62
+ const targetDir = dirname(target);
47
63
  mkdirSync(targetDir, { recursive: true });
48
64
  const temp = join(targetDir, `.${basename(source)}.rebind-${process.pid}.tmp`);
49
65
  writeFileSync(temp, `${JSON.stringify({ ...header, cwd })}\n${rest}`, { flag: "wx" });
50
- renameSync(temp, target);
51
- if (target !== source) {
66
+ if (target !== source)
52
67
  moveSessionSidecars(dirname(source), targetDir, header.id);
68
+ renameSync(temp, target);
69
+ if (target !== source)
53
70
  rmSync(source, { force: true });
54
- }
55
71
  return target;
56
72
  }
57
73
  function readHeaderLine(sessionFile) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-rebind.js","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { normalizePath, resolvePath } from \"../utils/paths.ts\";\nimport {\n\tcompareRepositoryIdentities,\n\tparseRepositoryIdentity,\n\tREPOSITORY_IDENTITY_ENTRY_TYPE,\n\ttype RepositoryIdentity,\n\ttype RepositoryMatch,\n\treadRepositoryIdentity,\n} from \"./repository-identity.ts\";\nimport { getDefaultSessionDir, loadEntriesFromFile } from \"./session-manager.ts\";\nimport { encodedSessionId } from \"./session-sidecar-store.ts\";\n\ninterface SessionHeaderLine {\n\treadonly header: { readonly type: \"session\"; readonly id: string; readonly cwd?: unknown };\n\treadonly rest: string;\n}\n\nexport function readRecordedRepositoryIdentity(sessionFile: string): RepositoryIdentity | undefined {\n\tlet recorded: RepositoryIdentity | undefined;\n\tfor (const entry of loadEntriesFromFile(sessionFile)) {\n\t\tif (entry.type === \"custom\" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {\n\t\t\trecorded = parseRepositoryIdentity(entry.data) ?? recorded;\n\t\t}\n\t}\n\treturn recorded;\n}\n\n/**\n * Whether `cwd` is the git repository a session was recorded in. The session side prefers the live\n * checkout at its recorded cwd and falls back to the identity the session recorded, because a moved\n * repository leaves nothing at the old path to ask.\n */\nexport async function classifySessionRepository(\n\tsessionFile: string,\n\tsessionCwd: string,\n\tcwd: string,\n\tread: (dir: string) => Promise<RepositoryIdentity | undefined> = readRepositoryIdentity,\n): Promise<RepositoryMatch> {\n\tconst [current, live] = await Promise.all([\n\t\tread(cwd),\n\t\texistsSync(sessionCwd) ? read(sessionCwd) : Promise.resolve(undefined),\n\t]);\n\treturn compareRepositoryIdentities(live ?? readRecordedRepositoryIdentity(sessionFile), current);\n}\n\nexport function readSessionCwd(sessionFile: string): string | undefined {\n\tconst { cwd } = readHeaderLine(resolvePath(sessionFile)).header;\n\treturn typeof cwd === \"string\" && cwd !== \"\" ? cwd : undefined;\n}\n\n/**\n * Moves a session recorded under another project into `targetCwd`'s session directory and points its\n * header at `targetCwd`. The id, file name, every entry after the header, and the session's extension\n * sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted\n * rebind leaves the session readable in at least one place. Returns the session file's new path.\n */\nexport function rebindSessionFile(sourcePath: string, targetCwd: string, sessionDir?: string): string {\n\tconst source = resolvePath(sourcePath);\n\tconst cwd = resolvePath(targetCwd);\n\tconst { header, rest } = readHeaderLine(source);\n\tconst targetDir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);\n\tconst target = join(targetDir, basename(source));\n\tif (target !== source && existsSync(target)) {\n\t\tthrow new Error(`Cannot rebind session: ${target} already exists`);\n\t}\n\tmkdirSync(targetDir, { recursive: true });\n\tconst temp = join(targetDir, `.${basename(source)}.rebind-${process.pid}.tmp`);\n\twriteFileSync(temp, `${JSON.stringify({ ...header, cwd })}\\n${rest}`, { flag: \"wx\" });\n\trenameSync(temp, target);\n\tif (target !== source) {\n\t\tmoveSessionSidecars(dirname(source), targetDir, header.id);\n\t\trmSync(source, { force: true });\n\t}\n\treturn target;\n}\n\nfunction readHeaderLine(sessionFile: string): SessionHeaderLine {\n\tconst content = readFileSync(sessionFile, \"utf8\");\n\tconst newline = content.indexOf(\"\\n\");\n\tconst firstLine = newline === -1 ? content : content.slice(0, newline);\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(firstLine);\n\t} catch {\n\t\tparsed = undefined;\n\t}\n\tif (!isSessionHeader(parsed)) {\n\t\tthrow new Error(`Cannot rebind session: ${sessionFile} does not start with a session header`);\n\t}\n\treturn { header: parsed, rest: newline === -1 ? \"\" : content.slice(newline + 1) };\n}\n\nfunction isSessionHeader(value: unknown): value is SessionHeaderLine[\"header\"] {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as { type?: unknown }).type === \"session\" &&\n\t\ttypeof (value as { id?: unknown }).id === \"string\"\n\t);\n}\n\n// Per-session extension state lives at `<sessionDir>/extensions/<extension>/<encoded id>.json`\n// (goal, loop, terminal monitors); without it a rebound session would lose its goal and monitors.\nfunction moveSessionSidecars(fromDir: string, toDir: string, sessionId: string): void {\n\tconst extensionsDir = join(fromDir, \"extensions\");\n\tconst sidecarName = `${encodedSessionId(sessionId)}.json`;\n\tif (existsSync(extensionsDir)) {\n\t\tfor (const extension of readdirSync(extensionsDir, { withFileTypes: true })) {\n\t\t\tif (!extension.isDirectory()) continue;\n\t\t\tconst from = join(extensionsDir, extension.name, sidecarName);\n\t\t\tif (existsSync(from)) movePath(from, join(toDir, \"extensions\", extension.name, sidecarName));\n\t\t}\n\t}\n\tconst blobs = join(fromDir, \"resident-blobs\", sessionId);\n\tif (existsSync(blobs)) movePath(blobs, join(toDir, \"resident-blobs\", sessionId));\n}\n\nfunction movePath(from: string, to: string): void {\n\tif (existsSync(to)) return;\n\tmkdirSync(dirname(to), { recursive: true });\n\ttry {\n\t\trenameSync(from, to);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"EXDEV\") throw error;\n\t\tcpSync(from, to, { recursive: true });\n\t\trmSync(from, { recursive: true, force: true });\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-rebind.js","sourceRoot":"","sources":["../../src/core/session-rebind.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { normalizePath, resolvePath } from \"../utils/paths.ts\";\nimport {\n\tcompareRepositoryIdentities,\n\tparseRepositoryIdentity,\n\tREPOSITORY_IDENTITY_ENTRY_TYPE,\n\ttype RepositoryIdentity,\n\ttype RepositoryMatch,\n\treadRepositoryIdentity,\n} from \"./repository-identity.ts\";\nimport { withSessionMoveLock } from \"./session-holders.ts\";\nimport { getDefaultSessionDir, loadEntriesFromFile } from \"./session-manager.ts\";\nimport { encodedSessionId } from \"./session-sidecar-store.ts\";\n\ninterface SessionHeaderLine {\n\treadonly header: { readonly type: \"session\"; readonly id: string; readonly cwd?: unknown };\n\treadonly rest: string;\n}\n\nexport function readRecordedRepositoryIdentity(sessionFile: string): RepositoryIdentity | undefined {\n\tlet recorded: RepositoryIdentity | undefined;\n\tfor (const entry of loadEntriesFromFile(sessionFile)) {\n\t\tif (entry.type === \"custom\" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {\n\t\t\trecorded = parseRepositoryIdentity(entry.data) ?? recorded;\n\t\t}\n\t}\n\treturn recorded;\n}\n\n/**\n * Whether `cwd` is the git repository a session was recorded in. The session side prefers the live\n * checkout at its recorded cwd and falls back to the identity the session recorded, because a moved\n * repository leaves nothing at the old path to ask.\n */\nexport async function classifySessionRepository(\n\tsessionFile: string,\n\tsessionCwd: string,\n\tcwd: string,\n\tread: (dir: string) => Promise<RepositoryIdentity | undefined> = readRepositoryIdentity,\n): Promise<RepositoryMatch> {\n\tconst [current, live] = await Promise.all([\n\t\tread(cwd),\n\t\texistsSync(sessionCwd) ? read(sessionCwd) : Promise.resolve(undefined),\n\t]);\n\treturn compareRepositoryIdentities(live ?? readRecordedRepositoryIdentity(sessionFile), current);\n}\n\nexport function readSessionCwd(sessionFile: string): string | undefined {\n\tconst { cwd } = readHeaderLine(resolvePath(sessionFile)).header;\n\treturn typeof cwd === \"string\" && cwd !== \"\" ? cwd : undefined;\n}\n\n/**\n * Moves a session recorded under another project into `targetCwd`'s session directory and points its\n * header at `targetCwd`. The id, file name, every entry after the header, and the session's extension\n * sidecars are kept. The relocated copy is complete before the source is removed, so an interrupted\n * rebind leaves the session readable in at least one place. Returns the session file's new path.\n *\n * The move runs under the session's cross-process move lock and refuses (`SessionHeldError`) while\n * another live process has the session open. A rebind that finds the same move already done by a\n * concurrent process returns that target.\n */\nexport async function rebindSessionFile(\n\tsourcePath: string,\n\ttargetCwd: string,\n\tsessionDir?: string,\n\toptions: { readonly moveLockWaitMs?: number } = {},\n): Promise<string> {\n\tconst source = resolvePath(sourcePath);\n\tconst cwd = resolvePath(targetCwd);\n\tconst targetDir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);\n\tconst target = join(targetDir, basename(source));\n\tif (target !== source && !existsSync(source) && existsSync(target)) return target;\n\tconst { header } = readHeaderLine(source);\n\treturn withSessionMoveLock(source, header.id, () => moveSessionFile(source, target, cwd), {\n\t\t...(options.moveLockWaitMs === undefined ? {} : { waitMs: options.moveLockWaitMs }),\n\t});\n}\n\nfunction moveSessionFile(source: string, target: string, cwd: string): string {\n\tif (target !== source && !existsSync(source) && existsSync(target)) return target;\n\tconst { header, rest } = readHeaderLine(source);\n\tif (target !== source && existsSync(target)) {\n\t\tthrow new Error(`Cannot rebind session: ${target} already exists`);\n\t}\n\tconst targetDir = dirname(target);\n\tmkdirSync(targetDir, { recursive: true });\n\tconst temp = join(targetDir, `.${basename(source)}.rebind-${process.pid}.tmp`);\n\twriteFileSync(temp, `${JSON.stringify({ ...header, cwd })}\\n${rest}`, { flag: \"wx\" });\n\tif (target !== source) moveSessionSidecars(dirname(source), targetDir, header.id);\n\trenameSync(temp, target);\n\tif (target !== source) rmSync(source, { force: true });\n\treturn target;\n}\n\nfunction readHeaderLine(sessionFile: string): SessionHeaderLine {\n\tconst content = readFileSync(sessionFile, \"utf8\");\n\tconst newline = content.indexOf(\"\\n\");\n\tconst firstLine = newline === -1 ? content : content.slice(0, newline);\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(firstLine);\n\t} catch {\n\t\tparsed = undefined;\n\t}\n\tif (!isSessionHeader(parsed)) {\n\t\tthrow new Error(`Cannot rebind session: ${sessionFile} does not start with a session header`);\n\t}\n\treturn { header: parsed, rest: newline === -1 ? \"\" : content.slice(newline + 1) };\n}\n\nfunction isSessionHeader(value: unknown): value is SessionHeaderLine[\"header\"] {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as { type?: unknown }).type === \"session\" &&\n\t\ttypeof (value as { id?: unknown }).id === \"string\"\n\t);\n}\n\n// Per-session extension state lives at `<sessionDir>/extensions/<extension>/<encoded id>.json`\n// (goal, loop, terminal monitors); without it a rebound session would lose its goal and monitors.\nfunction moveSessionSidecars(fromDir: string, toDir: string, sessionId: string): void {\n\tconst extensionsDir = join(fromDir, \"extensions\");\n\tconst sidecarName = `${encodedSessionId(sessionId)}.json`;\n\tif (existsSync(extensionsDir)) {\n\t\tfor (const extension of readdirSync(extensionsDir, { withFileTypes: true })) {\n\t\t\tif (!extension.isDirectory()) continue;\n\t\t\tconst from = join(extensionsDir, extension.name, sidecarName);\n\t\t\tif (existsSync(from)) movePath(from, join(toDir, \"extensions\", extension.name, sidecarName));\n\t\t}\n\t}\n\tconst blobs = join(fromDir, \"resident-blobs\", sessionId);\n\tif (existsSync(blobs)) movePath(blobs, join(toDir, \"resident-blobs\", sessionId));\n}\n\nfunction movePath(from: string, to: string): void {\n\tif (existsSync(to)) return;\n\tmkdirSync(dirname(to), { recursive: true });\n\ttry {\n\t\trenameSync(from, to);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"EXDEV\") throw error;\n\t\tcpSync(from, to, { recursive: true });\n\t\trmSync(from, { recursive: true, force: true });\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-summary-index-file.d.ts","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAKrE,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AA6BF,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,CAQ5G;AA6BD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BtE;AAED,4FAA4F;AAC5F,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,eAAe,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG"}
1
+ {"version":3,"file":"session-summary-index-file.d.ts","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAMrE,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AA8BF,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,CAQ5G;AA6BD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BtE;AAED,4FAA4F;AAC5F,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,eAAe,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG"}
@@ -1,11 +1,13 @@
1
1
  import { appendFile, rename, rm, writeFile } from "fs/promises";
2
+ import { parseRepositoryIdentity } from "./repository-identity.js";
2
3
  import { readFileLines } from "./session-summary.js";
3
4
  /**
4
5
  * Name of the summary index inside the sessions directory it describes. The
5
6
  * extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.
6
7
  */
7
8
  export const SESSION_SUMMARY_INDEX_FILE = ".session-summaries.index";
8
- const INDEX_VERSION = 1;
9
+ // 2: summaries carry the recorded repository identity; a version-1 index predates it and is re-read.
10
+ const INDEX_VERSION = 2;
9
11
  const HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\n`;
10
12
  const UNUSABLE = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };
11
13
  function isObject(value) {
@@ -23,7 +25,8 @@ function isSessionSummary(value) {
23
25
  typeof value.messageCount === "number" &&
24
26
  typeof value.allMessagesText === "string" &&
25
27
  isOptional(value.name, "string") &&
26
- isOptional(value.lastActivityTime, "number"));
28
+ isOptional(value.lastActivityTime, "number") &&
29
+ (value.repositoryIdentity === undefined || parseRepositoryIdentity(value.repositoryIdentity) !== undefined));
27
30
  }
28
31
  function isFiniteNumber(value) {
29
32
  return typeof value === "number" && Number.isFinite(value);
@@ -1 +1 @@
1
- {"version":3,"file":"session-summary-index-file.js","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, rename, rm, writeFile } from \"fs/promises\";\nimport { readFileLines, type SessionSummary } from \"./session-summary.ts\";\nimport type { FileStamp } from \"./session-summary-lru.ts\";\n\n/**\n * Name of the summary index inside the sessions directory it describes. The\n * extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.\n */\nexport const SESSION_SUMMARY_INDEX_FILE = \".session-summaries.index\";\n\nconst INDEX_VERSION = 1;\nconst HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\\n`;\n\n/** One indexed session file: its basename, the stamp it was read at, and its summary. */\nexport type IndexEntry = {\n\treadonly file: string;\n\treadonly stamp: FileStamp;\n\treadonly summary: SessionSummary;\n\t/** Serialized line, newline included. */\n\treadonly line: string;\n\treadonly lineBytes: number;\n};\n\n/** An index as loaded from disk; an absent or unusable file loads as `usable: false`. */\nexport type LoadedIndex = {\n\treadonly usable: boolean;\n\t/** Deduplicated entries, last line wins. */\n\treadonly entries: ReadonlyMap<string, IndexEntry>;\n\t/** Bytes after the header line, including torn and superseded lines. */\n\treadonly entryBytes: number;\n\t/** False when the file ends in a torn line, so the next append must start a new line. */\n\treadonly endsWithNewline: boolean;\n};\n\nconst UNUSABLE: LoadedIndex = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isOptional(value: unknown, type: \"string\" | \"number\"): boolean {\n\treturn value === undefined || typeof value === type;\n}\n\nfunction isSessionSummary(value: unknown): value is SessionSummary {\n\tif (!isObject(value) || !isObject(value.header)) return false;\n\treturn (\n\t\tvalue.header.type === \"session\" &&\n\t\ttypeof value.header.id === \"string\" &&\n\t\ttypeof value.firstUserMessage === \"string\" &&\n\t\ttypeof value.messageCount === \"number\" &&\n\t\ttypeof value.allMessagesText === \"string\" &&\n\t\tisOptional(value.name, \"string\") &&\n\t\tisOptional(value.lastActivityTime, \"number\")\n\t);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Serialize one entry, or undefined when the summary would not survive a JSON round trip. */\nexport function toIndexEntry(file: string, stamp: FileStamp, summary: SessionSummary): IndexEntry | undefined {\n\tif (summary.lastActivityTime !== undefined && !Number.isFinite(summary.lastActivityTime)) return undefined;\n\treturn withLine(\n\t\tfile,\n\t\tstamp,\n\t\tsummary,\n\t\t`${JSON.stringify({ file, size: stamp.size, mtimeMs: stamp.mtimeMs, summary })}\\n`,\n\t);\n}\n\nfunction withLine(file: string, stamp: FileStamp, summary: SessionSummary, line: string): IndexEntry {\n\treturn { file, stamp, summary, line, lineBytes: Buffer.byteLength(line, \"utf8\") };\n}\n\nfunction parseEntryLine(line: string): IndexEntry | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(line);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!isObject(parsed) || typeof parsed.file !== \"string\" || parsed.file.includes(\"/\")) return undefined;\n\tif (!isFiniteNumber(parsed.size) || !isFiniteNumber(parsed.mtimeMs) || !isSessionSummary(parsed.summary)) {\n\t\treturn undefined;\n\t}\n\treturn withLine(parsed.file, { size: parsed.size, mtimeMs: parsed.mtimeMs }, parsed.summary, `${line}\\n`);\n}\n\nfunction isCurrentHeader(line: string): boolean {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn isObject(parsed) && parsed.version === INDEX_VERSION;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Load an index, tolerating every failure: a missing file, a missing or foreign\n * version header, or a read error loads as unusable; a line that does not parse\n * (a torn append, truncation, garbage) is skipped.\n */\nexport async function loadIndexFile(path: string): Promise<LoadedIndex> {\n\tconst entries = new Map<string, IndexEntry>();\n\tlet headerSeen = false;\n\tlet headerBytes = 0;\n\ttry {\n\t\tconst read = await readFileLines(path, (line) => {\n\t\t\tif (!headerSeen) {\n\t\t\t\theaderSeen = true;\n\t\t\t\theaderBytes = Buffer.byteLength(line, \"utf8\") + 1;\n\t\t\t\treturn isCurrentHeader(line);\n\t\t\t}\n\t\t\tconst entry = parseEntryLine(line);\n\t\t\tif (entry) entries.set(entry.file, entry);\n\t\t\treturn true;\n\t\t});\n\t\tif (!read.completed || !headerSeen) return UNUSABLE;\n\t\treturn {\n\t\t\tusable: true,\n\t\t\tentries,\n\t\t\tentryBytes: Math.max(0, read.bytes - headerBytes),\n\t\t\tendsWithNewline: read.endsWithNewline,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof Error) return UNUSABLE;\n\t\tthrow error;\n\t}\n}\n\n/** Append entries one whole line per write, first closing a torn tail when there is one. */\nexport async function appendIndexEntries(\n\tpath: string,\n\tentries: readonly IndexEntry[],\n\tendsWithNewline: boolean,\n): Promise<void> {\n\tlet prefix = endsWithNewline ? \"\" : \"\\n\";\n\tfor (const entry of entries) {\n\t\tawait appendFile(path, prefix + entry.line);\n\t\tprefix = \"\";\n\t}\n}\n\nexport async function rewriteIndexFile(path: string, entries: readonly IndexEntry[]): Promise<void> {\n\tconst temp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n\ttry {\n\t\tawait writeFile(temp, HEADER_LINE + entries.map((entry) => entry.line).join(\"\"));\n\t\tawait rename(temp, path);\n\t} catch (error) {\n\t\tawait rm(temp, { force: true }).catch(() => undefined);\n\t\tthrow error;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-summary-index-file.js","sourceRoot":"","sources":["../../src/core/session-summary-index-file.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, rename, rm, writeFile } from \"fs/promises\";\nimport { parseRepositoryIdentity } from \"./repository-identity.ts\";\nimport { readFileLines, type SessionSummary } from \"./session-summary.ts\";\nimport type { FileStamp } from \"./session-summary-lru.ts\";\n\n/**\n * Name of the summary index inside the sessions directory it describes. The\n * extension is deliberately not `.jsonl`: every `*.jsonl` there is a session.\n */\nexport const SESSION_SUMMARY_INDEX_FILE = \".session-summaries.index\";\n\n// 2: summaries carry the recorded repository identity; a version-1 index predates it and is re-read.\nconst INDEX_VERSION = 2;\nconst HEADER_LINE = `${JSON.stringify({ version: INDEX_VERSION })}\\n`;\n\n/** One indexed session file: its basename, the stamp it was read at, and its summary. */\nexport type IndexEntry = {\n\treadonly file: string;\n\treadonly stamp: FileStamp;\n\treadonly summary: SessionSummary;\n\t/** Serialized line, newline included. */\n\treadonly line: string;\n\treadonly lineBytes: number;\n};\n\n/** An index as loaded from disk; an absent or unusable file loads as `usable: false`. */\nexport type LoadedIndex = {\n\treadonly usable: boolean;\n\t/** Deduplicated entries, last line wins. */\n\treadonly entries: ReadonlyMap<string, IndexEntry>;\n\t/** Bytes after the header line, including torn and superseded lines. */\n\treadonly entryBytes: number;\n\t/** False when the file ends in a torn line, so the next append must start a new line. */\n\treadonly endsWithNewline: boolean;\n};\n\nconst UNUSABLE: LoadedIndex = { usable: false, entries: new Map(), entryBytes: 0, endsWithNewline: true };\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isOptional(value: unknown, type: \"string\" | \"number\"): boolean {\n\treturn value === undefined || typeof value === type;\n}\n\nfunction isSessionSummary(value: unknown): value is SessionSummary {\n\tif (!isObject(value) || !isObject(value.header)) return false;\n\treturn (\n\t\tvalue.header.type === \"session\" &&\n\t\ttypeof value.header.id === \"string\" &&\n\t\ttypeof value.firstUserMessage === \"string\" &&\n\t\ttypeof value.messageCount === \"number\" &&\n\t\ttypeof value.allMessagesText === \"string\" &&\n\t\tisOptional(value.name, \"string\") &&\n\t\tisOptional(value.lastActivityTime, \"number\") &&\n\t\t(value.repositoryIdentity === undefined || parseRepositoryIdentity(value.repositoryIdentity) !== undefined)\n\t);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Serialize one entry, or undefined when the summary would not survive a JSON round trip. */\nexport function toIndexEntry(file: string, stamp: FileStamp, summary: SessionSummary): IndexEntry | undefined {\n\tif (summary.lastActivityTime !== undefined && !Number.isFinite(summary.lastActivityTime)) return undefined;\n\treturn withLine(\n\t\tfile,\n\t\tstamp,\n\t\tsummary,\n\t\t`${JSON.stringify({ file, size: stamp.size, mtimeMs: stamp.mtimeMs, summary })}\\n`,\n\t);\n}\n\nfunction withLine(file: string, stamp: FileStamp, summary: SessionSummary, line: string): IndexEntry {\n\treturn { file, stamp, summary, line, lineBytes: Buffer.byteLength(line, \"utf8\") };\n}\n\nfunction parseEntryLine(line: string): IndexEntry | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(line);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!isObject(parsed) || typeof parsed.file !== \"string\" || parsed.file.includes(\"/\")) return undefined;\n\tif (!isFiniteNumber(parsed.size) || !isFiniteNumber(parsed.mtimeMs) || !isSessionSummary(parsed.summary)) {\n\t\treturn undefined;\n\t}\n\treturn withLine(parsed.file, { size: parsed.size, mtimeMs: parsed.mtimeMs }, parsed.summary, `${line}\\n`);\n}\n\nfunction isCurrentHeader(line: string): boolean {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(line);\n\t\treturn isObject(parsed) && parsed.version === INDEX_VERSION;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Load an index, tolerating every failure: a missing file, a missing or foreign\n * version header, or a read error loads as unusable; a line that does not parse\n * (a torn append, truncation, garbage) is skipped.\n */\nexport async function loadIndexFile(path: string): Promise<LoadedIndex> {\n\tconst entries = new Map<string, IndexEntry>();\n\tlet headerSeen = false;\n\tlet headerBytes = 0;\n\ttry {\n\t\tconst read = await readFileLines(path, (line) => {\n\t\t\tif (!headerSeen) {\n\t\t\t\theaderSeen = true;\n\t\t\t\theaderBytes = Buffer.byteLength(line, \"utf8\") + 1;\n\t\t\t\treturn isCurrentHeader(line);\n\t\t\t}\n\t\t\tconst entry = parseEntryLine(line);\n\t\t\tif (entry) entries.set(entry.file, entry);\n\t\t\treturn true;\n\t\t});\n\t\tif (!read.completed || !headerSeen) return UNUSABLE;\n\t\treturn {\n\t\t\tusable: true,\n\t\t\tentries,\n\t\t\tentryBytes: Math.max(0, read.bytes - headerBytes),\n\t\t\tendsWithNewline: read.endsWithNewline,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof Error) return UNUSABLE;\n\t\tthrow error;\n\t}\n}\n\n/** Append entries one whole line per write, first closing a torn tail when there is one. */\nexport async function appendIndexEntries(\n\tpath: string,\n\tentries: readonly IndexEntry[],\n\tendsWithNewline: boolean,\n): Promise<void> {\n\tlet prefix = endsWithNewline ? \"\" : \"\\n\";\n\tfor (const entry of entries) {\n\t\tawait appendFile(path, prefix + entry.line);\n\t\tprefix = \"\";\n\t}\n}\n\nexport async function rewriteIndexFile(path: string, entries: readonly IndexEntry[]): Promise<void> {\n\tconst temp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n\ttry {\n\t\tawait writeFile(temp, HEADER_LINE + entries.map((entry) => entry.line).join(\"\"));\n\t\tawait rename(temp, path);\n\t} catch (error) {\n\t\tawait rm(temp, { force: true }).catch(() => undefined);\n\t\tthrow error;\n\t}\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type RepositoryIdentity } from "./repository-identity.ts";
1
2
  import type { SessionHeader } from "./session-manager.ts";
2
3
  /**
3
4
  * Everything a picker row needs from one session file, derived from every record
@@ -17,6 +18,8 @@ export type SessionSummary = {
17
18
  readonly lastActivityTime: number | undefined;
18
19
  /** Full user/assistant transcript text in file order. */
19
20
  readonly allMessagesText: string;
21
+ /** Latest repository identity the session recorded, so a moved repository's sessions can be found. */
22
+ readonly repositoryIdentity?: RepositoryIdentity;
20
23
  };
21
24
  /** What a completed {@link readFileLines} pass saw. */
22
25
  export type FileLinesRead = {
@@ -1 +1 @@
1
- {"version":3,"file":"session-summary.d.ts","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC,CAAC;AA8DF,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAmC/G;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAsBzF"}
1
+ {"version":3,"file":"session-summary.d.ts","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"AAEA,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,sGAAsG;IACtG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACjD,CAAC;AAqEF,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAmC/G;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAuBzF"}
@@ -1,5 +1,6 @@
1
1
  import { open } from "fs/promises";
2
2
  import { StringDecoder } from "string_decoder";
3
+ import { parseRepositoryIdentity, REPOSITORY_IDENTITY_ENTRY_TYPE, } from "./repository-identity.js";
3
4
  import { parseEntryLine, sessionInfoName, visibleMessage } from "./session-record.js";
4
5
  function newAccumulator() {
5
6
  return {
@@ -8,6 +9,7 @@ function newAccumulator() {
8
9
  firstUserMessage: "",
9
10
  messageCount: 0,
10
11
  lastActivityTime: undefined,
12
+ repositoryIdentity: undefined,
11
13
  texts: [],
12
14
  };
13
15
  }
@@ -27,6 +29,10 @@ function accumulateLine(accumulator, line) {
27
29
  accumulator.header = entry;
28
30
  return true;
29
31
  }
32
+ if (entry.type === "custom" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {
33
+ accumulator.repositoryIdentity = parseRepositoryIdentity(entry.data) ?? accumulator.repositoryIdentity;
34
+ return true;
35
+ }
30
36
  const infoName = sessionInfoName(entry);
31
37
  if (infoName !== null) {
32
38
  accumulator.name = infoName;
@@ -124,6 +130,7 @@ export async function readSessionSummary(filePath) {
124
130
  messageCount: accumulator.messageCount,
125
131
  lastActivityTime: accumulator.lastActivityTime,
126
132
  allMessagesText: accumulator.texts.join(" "),
133
+ ...(accumulator.repositoryIdentity ? { repositoryIdentity: accumulator.repositoryIdentity } : {}),
127
134
  };
128
135
  }
129
136
  //# sourceMappingURL=session-summary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-summary.js","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"fs/promises\";\nimport { StringDecoder } from \"string_decoder\";\nimport type { SessionHeader } from \"./session-manager.ts\";\nimport { parseEntryLine, sessionInfoName, visibleMessage } from \"./session-record.ts\";\n\n/**\n * Everything a picker row needs from one session file, derived from every record\n * in the file. Exact by construction: no window, no marker heuristic, so a first\n * user message, a rename, or an out-of-order activity timestamp is found no\n * matter how deep in the transcript it sits.\n */\nexport type SessionSummary = {\n\treadonly header: SessionHeader;\n\t/** Latest `session_info` name anywhere in the file, including explicit clears. */\n\treadonly name: string | undefined;\n\t/** First visible user message text anywhere in the file. */\n\treadonly firstUserMessage: string;\n\t/** Count of records that parsed as `type: \"message\"`. */\n\treadonly messageCount: number;\n\t/** Max user/assistant activity time seen, regardless of record order. */\n\treadonly lastActivityTime: number | undefined;\n\t/** Full user/assistant transcript text in file order. */\n\treadonly allMessagesText: string;\n};\n\ntype SummaryAccumulator = {\n\theader: SessionHeader | null;\n\tname: string | undefined;\n\tfirstUserMessage: string;\n\tmessageCount: number;\n\tlastActivityTime: number | undefined;\n\treadonly texts: string[];\n};\n\nfunction newAccumulator(): SummaryAccumulator {\n\treturn {\n\t\theader: null,\n\t\tname: undefined,\n\t\tfirstUserMessage: \"\",\n\t\tmessageCount: 0,\n\t\tlastActivityTime: undefined,\n\t\ttexts: [],\n\t};\n}\n\n/**\n * Fold one JSONL line into the accumulator.\n *\n * Returns false when the file's first parsable record is not a session header,\n * which means the file is not a session and the caller stops reading.\n */\nfunction accumulateLine(accumulator: SummaryAccumulator, line: string): boolean {\n\tconst entry = parseEntryLine(line);\n\tif (!entry) return true;\n\n\tif (!accumulator.header) {\n\t\tif (entry.type !== \"session\" || typeof entry.id !== \"string\") return false;\n\t\taccumulator.header = entry;\n\t\treturn true;\n\t}\n\n\tconst infoName = sessionInfoName(entry);\n\tif (infoName !== null) {\n\t\taccumulator.name = infoName;\n\t\treturn true;\n\t}\n\n\tif (entry.type !== \"message\") return true;\n\taccumulator.messageCount++;\n\n\tconst visible = visibleMessage(entry);\n\tif (!visible) return true;\n\tif (typeof visible.time === \"number\") {\n\t\taccumulator.lastActivityTime = Math.max(accumulator.lastActivityTime ?? 0, visible.time);\n\t}\n\tif (!visible.text) return true;\n\taccumulator.texts.push(visible.text);\n\tif (!accumulator.firstUserMessage && visible.role === \"user\") {\n\t\taccumulator.firstUserMessage = visible.text;\n\t}\n\treturn true;\n}\n\nconst LINE_READ_BUFFER_SIZE = 1024 * 1024;\n\n/** What a completed {@link readFileLines} pass saw. */\nexport type FileLinesRead = {\n\t/** False when `onLine` stopped the pass early. */\n\treadonly completed: boolean;\n\t/** Bytes read from the file. */\n\treadonly bytes: number;\n\t/** Whether the last byte read was a newline (a torn tail has none). */\n\treadonly endsWithNewline: boolean;\n};\n\n/**\n * Read a file line by line through one reused 1 MiB buffer, handing each line\n * (LF-split, a trailing CR dropped) to `onLine` until it returns false.\n *\n * This replaces `readline`, whose per-line async iteration dominated summary\n * cost (senpi#2087). Only the unfinished tail of a chunk is carried between\n * reads, and the next newline search resumes where the carried tail ends, so a\n * record larger than the buffer is not rescanned per chunk. Read errors throw.\n */\nexport async function readFileLines(filePath: string, onLine: (line: string) => boolean): Promise<FileLinesRead> {\n\tconst handle = await open(filePath, \"r\");\n\ttry {\n\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\tconst buffer = Buffer.allocUnsafe(LINE_READ_BUFFER_SIZE);\n\t\tlet pending = \"\";\n\t\tlet bytes = 0;\n\t\tlet lastByte = -1;\n\t\tconst emit = (line: string): boolean => onLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line);\n\n\t\twhile (true) {\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n\t\t\tif (bytesRead === 0) break;\n\t\t\tbytes += bytesRead;\n\t\t\tlastByte = buffer[bytesRead - 1] ?? -1;\n\n\t\t\tconst searchFrom = pending.length;\n\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\tlet lineStart = 0;\n\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", searchFrom);\n\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\tif (!emit(pending.slice(lineStart, newlineIndex)))\n\t\t\t\t\treturn { completed: false, bytes, endsWithNewline: false };\n\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t}\n\t\t\tpending = pending.slice(lineStart);\n\t\t}\n\n\t\tpending += decoder.end();\n\t\tif (pending && !emit(pending)) return { completed: false, bytes, endsWithNewline: false };\n\t\treturn { completed: true, bytes, endsWithNewline: lastByte === 0x0a };\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\n/**\n * Read one session file line by line and fold it into an exact summary.\n *\n * Memory stays bounded by one read buffer plus the transcript text the summary\n * contract requires; no full-file string is ever materialized. A file whose\n * first record is not a session header, or that cannot be read, yields null.\n */\nexport async function readSessionSummary(filePath: string): Promise<SessionSummary | null> {\n\tconst accumulator = newAccumulator();\n\n\ttry {\n\t\tconst read = await readFileLines(filePath, (line) => accumulateLine(accumulator, line));\n\t\tif (!read.completed) return null;\n\t} catch (error) {\n\t\tif (error instanceof Error) return null;\n\t\tthrow error;\n\t}\n\n\tconst header = accumulator.header;\n\tif (!header) return null;\n\n\treturn {\n\t\theader,\n\t\tname: accumulator.name,\n\t\tfirstUserMessage: accumulator.firstUserMessage,\n\t\tmessageCount: accumulator.messageCount,\n\t\tlastActivityTime: accumulator.lastActivityTime,\n\t\tallMessagesText: accumulator.texts.join(\" \"),\n\t};\n}\n"]}
1
+ {"version":3,"file":"session-summary.js","sourceRoot":"","sources":["../../src/core/session-summary.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"fs/promises\";\nimport { StringDecoder } from \"string_decoder\";\nimport {\n\tparseRepositoryIdentity,\n\tREPOSITORY_IDENTITY_ENTRY_TYPE,\n\ttype RepositoryIdentity,\n} from \"./repository-identity.ts\";\nimport type { SessionHeader } from \"./session-manager.ts\";\nimport { parseEntryLine, sessionInfoName, visibleMessage } from \"./session-record.ts\";\n\n/**\n * Everything a picker row needs from one session file, derived from every record\n * in the file. Exact by construction: no window, no marker heuristic, so a first\n * user message, a rename, or an out-of-order activity timestamp is found no\n * matter how deep in the transcript it sits.\n */\nexport type SessionSummary = {\n\treadonly header: SessionHeader;\n\t/** Latest `session_info` name anywhere in the file, including explicit clears. */\n\treadonly name: string | undefined;\n\t/** First visible user message text anywhere in the file. */\n\treadonly firstUserMessage: string;\n\t/** Count of records that parsed as `type: \"message\"`. */\n\treadonly messageCount: number;\n\t/** Max user/assistant activity time seen, regardless of record order. */\n\treadonly lastActivityTime: number | undefined;\n\t/** Full user/assistant transcript text in file order. */\n\treadonly allMessagesText: string;\n\t/** Latest repository identity the session recorded, so a moved repository's sessions can be found. */\n\treadonly repositoryIdentity?: RepositoryIdentity;\n};\n\ntype SummaryAccumulator = {\n\theader: SessionHeader | null;\n\tname: string | undefined;\n\tfirstUserMessage: string;\n\tmessageCount: number;\n\tlastActivityTime: number | undefined;\n\trepositoryIdentity: RepositoryIdentity | undefined;\n\treadonly texts: string[];\n};\n\nfunction newAccumulator(): SummaryAccumulator {\n\treturn {\n\t\theader: null,\n\t\tname: undefined,\n\t\tfirstUserMessage: \"\",\n\t\tmessageCount: 0,\n\t\tlastActivityTime: undefined,\n\t\trepositoryIdentity: undefined,\n\t\ttexts: [],\n\t};\n}\n\n/**\n * Fold one JSONL line into the accumulator.\n *\n * Returns false when the file's first parsable record is not a session header,\n * which means the file is not a session and the caller stops reading.\n */\nfunction accumulateLine(accumulator: SummaryAccumulator, line: string): boolean {\n\tconst entry = parseEntryLine(line);\n\tif (!entry) return true;\n\n\tif (!accumulator.header) {\n\t\tif (entry.type !== \"session\" || typeof entry.id !== \"string\") return false;\n\t\taccumulator.header = entry;\n\t\treturn true;\n\t}\n\n\tif (entry.type === \"custom\" && entry.customType === REPOSITORY_IDENTITY_ENTRY_TYPE) {\n\t\taccumulator.repositoryIdentity = parseRepositoryIdentity(entry.data) ?? accumulator.repositoryIdentity;\n\t\treturn true;\n\t}\n\n\tconst infoName = sessionInfoName(entry);\n\tif (infoName !== null) {\n\t\taccumulator.name = infoName;\n\t\treturn true;\n\t}\n\n\tif (entry.type !== \"message\") return true;\n\taccumulator.messageCount++;\n\n\tconst visible = visibleMessage(entry);\n\tif (!visible) return true;\n\tif (typeof visible.time === \"number\") {\n\t\taccumulator.lastActivityTime = Math.max(accumulator.lastActivityTime ?? 0, visible.time);\n\t}\n\tif (!visible.text) return true;\n\taccumulator.texts.push(visible.text);\n\tif (!accumulator.firstUserMessage && visible.role === \"user\") {\n\t\taccumulator.firstUserMessage = visible.text;\n\t}\n\treturn true;\n}\n\nconst LINE_READ_BUFFER_SIZE = 1024 * 1024;\n\n/** What a completed {@link readFileLines} pass saw. */\nexport type FileLinesRead = {\n\t/** False when `onLine` stopped the pass early. */\n\treadonly completed: boolean;\n\t/** Bytes read from the file. */\n\treadonly bytes: number;\n\t/** Whether the last byte read was a newline (a torn tail has none). */\n\treadonly endsWithNewline: boolean;\n};\n\n/**\n * Read a file line by line through one reused 1 MiB buffer, handing each line\n * (LF-split, a trailing CR dropped) to `onLine` until it returns false.\n *\n * This replaces `readline`, whose per-line async iteration dominated summary\n * cost (senpi#2087). Only the unfinished tail of a chunk is carried between\n * reads, and the next newline search resumes where the carried tail ends, so a\n * record larger than the buffer is not rescanned per chunk. Read errors throw.\n */\nexport async function readFileLines(filePath: string, onLine: (line: string) => boolean): Promise<FileLinesRead> {\n\tconst handle = await open(filePath, \"r\");\n\ttry {\n\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\tconst buffer = Buffer.allocUnsafe(LINE_READ_BUFFER_SIZE);\n\t\tlet pending = \"\";\n\t\tlet bytes = 0;\n\t\tlet lastByte = -1;\n\t\tconst emit = (line: string): boolean => onLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line);\n\n\t\twhile (true) {\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n\t\t\tif (bytesRead === 0) break;\n\t\t\tbytes += bytesRead;\n\t\t\tlastByte = buffer[bytesRead - 1] ?? -1;\n\n\t\t\tconst searchFrom = pending.length;\n\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\tlet lineStart = 0;\n\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", searchFrom);\n\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\tif (!emit(pending.slice(lineStart, newlineIndex)))\n\t\t\t\t\treturn { completed: false, bytes, endsWithNewline: false };\n\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t}\n\t\t\tpending = pending.slice(lineStart);\n\t\t}\n\n\t\tpending += decoder.end();\n\t\tif (pending && !emit(pending)) return { completed: false, bytes, endsWithNewline: false };\n\t\treturn { completed: true, bytes, endsWithNewline: lastByte === 0x0a };\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\n/**\n * Read one session file line by line and fold it into an exact summary.\n *\n * Memory stays bounded by one read buffer plus the transcript text the summary\n * contract requires; no full-file string is ever materialized. A file whose\n * first record is not a session header, or that cannot be read, yields null.\n */\nexport async function readSessionSummary(filePath: string): Promise<SessionSummary | null> {\n\tconst accumulator = newAccumulator();\n\n\ttry {\n\t\tconst read = await readFileLines(filePath, (line) => accumulateLine(accumulator, line));\n\t\tif (!read.completed) return null;\n\t} catch (error) {\n\t\tif (error instanceof Error) return null;\n\t\tthrow error;\n\t}\n\n\tconst header = accumulator.header;\n\tif (!header) return null;\n\n\treturn {\n\t\theader,\n\t\tname: accumulator.name,\n\t\tfirstUserMessage: accumulator.firstUserMessage,\n\t\tmessageCount: accumulator.messageCount,\n\t\tlastActivityTime: accumulator.lastActivityTime,\n\t\tallMessagesText: accumulator.texts.join(\" \"),\n\t\t...(accumulator.repositoryIdentity ? { repositoryIdentity: accumulator.repositoryIdentity } : {}),\n\t};\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export declare function isRegenerableEntry(entry: string): boolean;
2
+ export declare function pathsPointToSameLocation(leftPath: string, rightPath: string): boolean;
3
+ export declare function isWithinOrSamePath(childPath: string, parentPath: string): boolean;
4
+ /**
5
+ * Copies the top-level entries of `source` that `target` lacks. Never overwrites and only reads
6
+ * `source`; clones copy-on-write where supported so large session trees cost no extra space.
7
+ */
8
+ export declare function copyMissingEntries(source: string, target: string, include: (entry: string) => boolean): readonly string[];
9
+ //# sourceMappingURL=legacy-dir-copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-dir-copy.d.ts","sourceRoot":"","sources":["../src/legacy-dir-copy.ts"],"names":[],"mappings":"AAMA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAOrF;AAUD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAGjF;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GACjC,SAAS,MAAM,EAAE,CA4BnB"}
@@ -0,0 +1,69 @@
1
+ import { constants, cpSync, existsSync, mkdirSync, readdirSync, realpathSync } from "node:fs";
2
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import chalk from "chalk";
4
+ const REGENERABLE_ENTRIES = new Set(["cache", "logs", "tmp", "migrations-state.json"]);
5
+ export function isRegenerableEntry(entry) {
6
+ return REGENERABLE_ENTRIES.has(entry) || entry.endsWith(".log");
7
+ }
8
+ export function pathsPointToSameLocation(leftPath, rightPath) {
9
+ try {
10
+ return realpathSync(leftPath) === realpathSync(rightPath);
11
+ }
12
+ catch (error) {
13
+ if (isMissingPathError(error))
14
+ return false;
15
+ throw error;
16
+ }
17
+ }
18
+ /** Resolves symlinks in the longest existing prefix, so `/tmp/x` and `/private/tmp/x` compare equal. */
19
+ function canonicalPath(path) {
20
+ const absolute = resolve(path);
21
+ if (existsSync(absolute))
22
+ return realpathSync(absolute);
23
+ const parent = dirname(absolute);
24
+ return parent === absolute ? absolute : join(canonicalPath(parent), basename(absolute));
25
+ }
26
+ export function isWithinOrSamePath(childPath, parentPath) {
27
+ const relativePath = relative(canonicalPath(parentPath), canonicalPath(childPath));
28
+ return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
29
+ }
30
+ function isMissingPathError(error) {
31
+ return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
32
+ }
33
+ /**
34
+ * Copies the top-level entries of `source` that `target` lacks. Never overwrites and only reads
35
+ * `source`; clones copy-on-write where supported so large session trees cost no extra space.
36
+ */
37
+ export function copyMissingEntries(source, target, include) {
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(source);
41
+ }
42
+ catch (error) {
43
+ if (isMissingPathError(error))
44
+ return [];
45
+ throw error;
46
+ }
47
+ const copied = [];
48
+ for (const entry of entries) {
49
+ if (!include(entry) || existsSync(join(target, entry)))
50
+ continue;
51
+ try {
52
+ mkdirSync(target, { recursive: true });
53
+ cpSync(join(source, entry), join(target, entry), {
54
+ recursive: true,
55
+ force: false,
56
+ errorOnExist: false,
57
+ preserveTimestamps: true,
58
+ mode: constants.COPYFILE_FICLONE,
59
+ });
60
+ copied.push(entry);
61
+ }
62
+ catch (error) {
63
+ const reason = error instanceof Error ? error.message : String(error);
64
+ console.warn(chalk.yellow(`Could not copy ${join(source, entry)} → ${join(target, entry)}: ${reason}`));
65
+ }
66
+ }
67
+ return copied;
68
+ }
69
+ //# sourceMappingURL=legacy-dir-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-dir-copy.js","sourceRoot":"","sources":["../src/legacy-dir-copy.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants, cpSync, existsSync, mkdirSync, readdirSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport chalk from \"chalk\";\n\nconst REGENERABLE_ENTRIES: ReadonlySet<string> = new Set([\"cache\", \"logs\", \"tmp\", \"migrations-state.json\"]);\n\nexport function isRegenerableEntry(entry: string): boolean {\n\treturn REGENERABLE_ENTRIES.has(entry) || entry.endsWith(\".log\");\n}\n\nexport function pathsPointToSameLocation(leftPath: string, rightPath: string): boolean {\n\ttry {\n\t\treturn realpathSync(leftPath) === realpathSync(rightPath);\n\t} catch (error) {\n\t\tif (isMissingPathError(error)) return false;\n\t\tthrow error;\n\t}\n}\n\n/** Resolves symlinks in the longest existing prefix, so `/tmp/x` and `/private/tmp/x` compare equal. */\nfunction canonicalPath(path: string): string {\n\tconst absolute = resolve(path);\n\tif (existsSync(absolute)) return realpathSync(absolute);\n\tconst parent = dirname(absolute);\n\treturn parent === absolute ? absolute : join(canonicalPath(parent), basename(absolute));\n}\n\nexport function isWithinOrSamePath(childPath: string, parentPath: string): boolean {\n\tconst relativePath = relative(canonicalPath(parentPath), canonicalPath(childPath));\n\treturn relativePath === \"\" || (!relativePath.startsWith(\"..\") && !isAbsolute(relativePath));\n}\n\nfunction isMissingPathError(error: unknown): boolean {\n\treturn error instanceof Error && \"code\" in error && (error.code === \"ENOENT\" || error.code === \"ENOTDIR\");\n}\n\n/**\n * Copies the top-level entries of `source` that `target` lacks. Never overwrites and only reads\n * `source`; clones copy-on-write where supported so large session trees cost no extra space.\n */\nexport function copyMissingEntries(\n\tsource: string,\n\ttarget: string,\n\tinclude: (entry: string) => boolean,\n): readonly string[] {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(source);\n\t} catch (error) {\n\t\tif (isMissingPathError(error)) return [];\n\t\tthrow error;\n\t}\n\n\tconst copied: string[] = [];\n\tfor (const entry of entries) {\n\t\tif (!include(entry) || existsSync(join(target, entry))) continue;\n\t\ttry {\n\t\t\tmkdirSync(target, { recursive: true });\n\t\t\tcpSync(join(source, entry), join(target, entry), {\n\t\t\t\trecursive: true,\n\t\t\t\tforce: false,\n\t\t\t\terrorOnExist: false,\n\t\t\t\tpreserveTimestamps: true,\n\t\t\t\tmode: constants.COPYFILE_FICLONE,\n\t\t\t});\n\t\t\tcopied.push(entry);\n\t\t} catch (error) {\n\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\tconsole.warn(chalk.yellow(`Could not copy ${join(source, entry)} → ${join(target, entry)}: ${reason}`));\n\t\t}\n\t}\n\treturn copied;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-senpi-dir-migration.d.ts","sourceRoot":"","sources":["../src/legacy-senpi-dir-migration.ts"],"names":[],"mappings":"AAyDA,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA0BxD"}
1
+ {"version":3,"file":"legacy-senpi-dir-migration.d.ts","sourceRoot":"","sources":["../src/legacy-senpi-dir-migration.ts"],"names":[],"mappings":"AAuEA,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA8CxD"}
@@ -1,57 +1,53 @@
1
1
  import os from "node:os";
2
2
  import chalk from "chalk";
3
- import { existsSync, mkdirSync, readdirSync, realpathSync, renameSync } from "fs";
4
- import { dirname, isAbsolute, join, relative, resolve } from "path";
3
+ import { existsSync, mkdirSync, readdirSync, renameSync } from "fs";
4
+ import { dirname, join } from "path";
5
5
  import { CONFIG_DIR_NAME, getAgentDir } from "./config.js";
6
- function pathsPointToSameLocation(leftPath, rightPath) {
7
- try {
8
- return realpathSync(leftPath) === realpathSync(rightPath);
9
- }
10
- catch {
11
- return false;
6
+ import { copyMissingEntries, isRegenerableEntry, isWithinOrSamePath, pathsPointToSameLocation, } from "./legacy-dir-copy.js";
7
+ function copyLegacyDir({ from, to, label }) {
8
+ const copied = copyMissingEntries(from, to, (entry) => !isRegenerableEntry(entry));
9
+ if (copied.length > 0) {
10
+ console.log(chalk.green(`Copied ${label} ${from} → ${to}`));
11
+ console.log(chalk.dim("The original directory is untouched; the two installs keep separate state from now on."));
12
12
  }
13
13
  }
14
- function isWithinOrSamePath(childPath, parentPath) {
15
- const relativePath = relative(resolve(parentPath), resolve(childPath));
16
- return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
17
- }
18
- function migratePathPreservingExisting(oldPath, newPath, label) {
19
- if (!existsSync(oldPath))
20
- return;
21
- if (existsSync(newPath) && pathsPointToSameLocation(oldPath, newPath))
22
- return;
23
- if (!existsSync(newPath)) {
24
- try {
25
- mkdirSync(dirname(newPath), { recursive: true });
26
- renameSync(oldPath, newPath);
27
- console.log(chalk.green(`Migrated ${label} ${oldPath} → ${newPath}`));
28
- }
29
- catch {
30
- return;
31
- }
32
- return;
33
- }
34
- let entries;
35
- try {
36
- entries = readdirSync(oldPath);
37
- }
38
- catch {
14
+ function moveLegacyDir({ from, to, label }) {
15
+ if (!existsSync(to)) {
16
+ mkdirSync(dirname(to), { recursive: true });
17
+ renameSync(from, to);
18
+ console.log(chalk.green(`Migrated ${label} ${from} → ${to}`));
39
19
  return;
40
20
  }
41
21
  let movedAny = false;
42
- for (const entry of entries) {
43
- const source = join(oldPath, entry);
44
- const target = join(newPath, entry);
22
+ for (const entry of readdirSync(from)) {
23
+ const target = join(to, entry);
45
24
  if (existsSync(target))
46
25
  continue;
47
- try {
48
- renameSync(source, target);
49
- movedAny = true;
50
- }
51
- catch { }
26
+ renameSync(join(from, entry), target);
27
+ movedAny = true;
52
28
  }
53
29
  if (movedAny) {
54
- console.log(chalk.green(`Migrated missing ${label} entries ${oldPath} → ${newPath}`));
30
+ console.log(chalk.green(`Migrated missing ${label} entries ${from} → ${to}`));
31
+ }
32
+ }
33
+ function transferLegacyDir(dir) {
34
+ if (!existsSync(dir.from))
35
+ return;
36
+ if (existsSync(dir.to) && pathsPointToSameLocation(dir.from, dir.to))
37
+ return;
38
+ try {
39
+ switch (dir.transfer) {
40
+ case "copy":
41
+ copyLegacyDir(dir);
42
+ return;
43
+ case "move":
44
+ moveLegacyDir(dir);
45
+ return;
46
+ }
47
+ }
48
+ catch (error) {
49
+ const reason = error instanceof Error ? error.message : String(error);
50
+ console.warn(chalk.yellow(`Skipped the ${dir.label} ${dir.from}: ${reason}`));
55
51
  }
56
52
  }
57
53
  export function migrateLegacySenpiDirs(cwd) {
@@ -62,15 +58,35 @@ export function migrateLegacySenpiDirs(cwd) {
62
58
  const globalNewMomDir = join(homeDir, CONFIG_DIR_NAME, "mom");
63
59
  const projectNewDir = join(cwd, CONFIG_DIR_NAME);
64
60
  const shouldMigrateHomeConfig = isWithinOrSamePath(globalNewAgentDir, join(homeDir, CONFIG_DIR_NAME));
65
- const moves = [
66
- [join(cwd, ".pi"), projectNewDir, "project config directory"],
67
- [join(cwd, CONFIG_DIR_NAME, ".pi"), projectNewDir, "nested project config directory"],
61
+ const dirs = [
62
+ { from: join(cwd, ".pi"), to: projectNewDir, label: "project config directory", transfer: "copy" },
63
+ {
64
+ from: join(cwd, CONFIG_DIR_NAME, ".pi"),
65
+ to: projectNewDir,
66
+ label: "nested project config directory",
67
+ transfer: "move",
68
+ },
68
69
  ];
69
70
  if (shouldMigrateHomeConfig) {
70
- moves.unshift([join(homeDir, ".pi", "agent"), globalNewAgentDir, "global agent directory"], [join(homeDir, CONFIG_DIR_NAME, ".pi", "agent"), globalNewAgentDir, "nested global agent directory"], [join(homeDir, ".pi", "mom"), globalNewMomDir, "global mom directory"], [join(homeDir, CONFIG_DIR_NAME, ".pi", "mom"), globalNewMomDir, "nested global mom directory"]);
71
+ dirs.unshift({
72
+ from: join(homeDir, ".pi", "agent"),
73
+ to: globalNewAgentDir,
74
+ label: "global agent directory",
75
+ transfer: "copy",
76
+ }, {
77
+ from: join(homeDir, CONFIG_DIR_NAME, ".pi", "agent"),
78
+ to: globalNewAgentDir,
79
+ label: "nested global agent directory",
80
+ transfer: "move",
81
+ }, { from: join(homeDir, ".pi", "mom"), to: globalNewMomDir, label: "global mom directory", transfer: "copy" }, {
82
+ from: join(homeDir, CONFIG_DIR_NAME, ".pi", "mom"),
83
+ to: globalNewMomDir,
84
+ label: "nested global mom directory",
85
+ transfer: "move",
86
+ });
71
87
  }
72
- for (const [oldPath, newPath, label] of moves) {
73
- migratePathPreservingExisting(oldPath, newPath, label);
88
+ for (const dir of dirs) {
89
+ transferLegacyDir(dir);
74
90
  }
75
91
  }
76
92
  //# sourceMappingURL=legacy-senpi-dir-migration.js.map