@wrongstack/core 0.313.1 → 0.316.1

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 (290) hide show
  1. package/dist/chronicle/index.js +31 -31
  2. package/dist/chronicle/legacy-journal-import.d.ts +0 -6
  3. package/dist/chronicle/metrics-ingest.d.ts +14 -0
  4. package/dist/chronicle/metrics-schema.d.ts +10 -1
  5. package/dist/chronicle/metrics-store.d.ts +20 -2
  6. package/dist/chronicle/project-server.js +39 -31
  7. package/dist/chronicle/sink.d.ts +13 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +20 -2
  9. package/dist/{chunk-WOEZV3Q4.js → chunk-25LSLC3G.js} +2 -1
  10. package/dist/{chunk-AI4LLQ2U.js → chunk-27QIURLN.js} +229 -12
  11. package/dist/{chunk-I45KAW2N.js → chunk-3AGWVSS7.js} +38 -450
  12. package/dist/chunk-3BDV7RL3.js +211 -0
  13. package/dist/{chunk-NFAYHPRH.js → chunk-3ECENVHL.js} +10 -8
  14. package/dist/{chunk-FW5LDDMJ.js → chunk-45WI72ZB.js} +1576 -1156
  15. package/dist/{chunk-AT4V4XGI.js → chunk-4BMVPOHO.js} +539 -313
  16. package/dist/{chunk-YBUJCZ57.js → chunk-4I7HGVVA.js} +17 -5
  17. package/dist/{chunk-JVGPR3TS.js → chunk-4LCZVJVF.js} +7 -55
  18. package/dist/{chunk-4ZHTKJHM.js → chunk-4SJNS7OB.js} +36 -2
  19. package/dist/{chunk-AD7ZFZCS.js → chunk-5BMIE7QG.js} +3 -3
  20. package/dist/{chunk-IQ35HMQL.js → chunk-5YPSXQXM.js} +71 -24
  21. package/dist/chunk-6FHWXLPT.js +10 -0
  22. package/dist/{chunk-FIV4IJL7.js → chunk-6H5GEOET.js} +163 -20
  23. package/dist/{chunk-VB3H5WWB.js → chunk-6NWEVFEL.js} +2 -2
  24. package/dist/{chunk-E6I5RXVK.js → chunk-AYW5YINX.js} +2 -2
  25. package/dist/{chunk-DEIQMBD2.js → chunk-B7F4WVP4.js} +2 -2
  26. package/dist/{chunk-XQ2XGIBF.js → chunk-BPBKKBUK.js} +6 -7
  27. package/dist/{chunk-K4YUNKU7.js → chunk-BYD345OX.js} +2 -2
  28. package/dist/{chunk-LZDDLNCZ.js → chunk-C27KDLOX.js} +4 -4
  29. package/dist/{chunk-6AKOTGLM.js → chunk-CU546ESW.js} +7 -7
  30. package/dist/{chunk-ZZ4V3WIZ.js → chunk-DLHHXUAM.js} +10 -1
  31. package/dist/{chunk-BAKVJLEV.js → chunk-DOTHQSJ2.js} +28 -16
  32. package/dist/{chunk-KBZGE6LN.js → chunk-DYLPMV4Q.js} +4 -3
  33. package/dist/{chunk-52RTVH4M.js → chunk-F3JWNST7.js} +2 -2
  34. package/dist/{chunk-WEBMSQP2.js → chunk-FC2ZGRF3.js} +21 -4
  35. package/dist/{chunk-FHAMQS7Q.js → chunk-FCBTO76Z.js} +76 -146
  36. package/dist/{chunk-4LJJYBAU.js → chunk-FHZVFYSL.js} +9 -1
  37. package/dist/{chunk-W4A6QA3D.js → chunk-FWNLEZYG.js} +4 -3
  38. package/dist/{chunk-OJZEX73F.js → chunk-GDQ7CZRQ.js} +5 -1
  39. package/dist/{chunk-QVOVMXJR.js → chunk-GJJGJB2R.js} +32 -9
  40. package/dist/{chunk-4XV6IR37.js → chunk-HI3JGZR2.js} +7 -6
  41. package/dist/{chunk-FSWCWSTS.js → chunk-HMLIGAU7.js} +26 -26
  42. package/dist/{chunk-PFXFQQRT.js → chunk-HXKZ7OBQ.js} +7 -43
  43. package/dist/{chunk-2FQL4PDC.js → chunk-I7DALG7J.js} +5 -5
  44. package/dist/{chunk-GM54GJBU.js → chunk-IRUTEHCE.js} +3 -3
  45. package/dist/{chunk-TYW2GI5I.js → chunk-J2WRWR37.js} +40 -13
  46. package/dist/{chunk-ZEHUCJHY.js → chunk-JFPJ6LLQ.js} +2 -2
  47. package/dist/chunk-JMULFFLN.js +9 -0
  48. package/dist/{chunk-RD6DEE36.js → chunk-JO4MGB6Z.js} +94 -8
  49. package/dist/{chunk-5DSMEVA4.js → chunk-K6BXGITA.js} +7 -7
  50. package/dist/{chunk-O74MJHXU.js → chunk-KZMSZV32.js} +2 -2
  51. package/dist/{chunk-GFJOMSHG.js → chunk-LA7CLDRW.js} +6 -2
  52. package/dist/{chunk-M2WF6AXG.js → chunk-LSMMOS2Z.js} +7 -3
  53. package/dist/{chunk-ASX6AW36.js → chunk-LXWCMQNJ.js} +3 -3
  54. package/dist/{chunk-WZO23AWO.js → chunk-M27LIEKV.js} +72 -22
  55. package/dist/{chunk-JTHKOYWU.js → chunk-MJPHJRAL.js} +15 -15
  56. package/dist/{chunk-G5AAKEEZ.js → chunk-MTL4O5YR.js} +5 -5
  57. package/dist/chunk-NHJ75HXY.js +1 -0
  58. package/dist/{chunk-ZYU2YOJ6.js → chunk-NIOAGFPB.js} +29 -15
  59. package/dist/{chunk-Y2GE5TSI.js → chunk-NS52ISWS.js} +2 -2
  60. package/dist/{chunk-UIWONWVG.js → chunk-NSOFPKGT.js} +541 -148
  61. package/dist/{chunk-RCQYT2I4.js → chunk-NVIZG63R.js} +2 -2
  62. package/dist/{chunk-EPC3GZD4.js → chunk-OOYZPOXM.js} +2 -2
  63. package/dist/{chunk-7ZHZE5EO.js → chunk-OSGLYW5J.js} +2 -2
  64. package/dist/{chunk-CE4ZOCOI.js → chunk-PFCMSSUF.js} +9 -9
  65. package/dist/{chunk-GFXZTHNI.js → chunk-PGRVXIMY.js} +5 -5
  66. package/dist/{chunk-JJPMWPB6.js → chunk-PISBFBJN.js} +6 -6
  67. package/dist/chunk-POOOIWE5.js +9 -0
  68. package/dist/{chunk-5RP3QTRL.js → chunk-PQNS3U7Z.js} +20 -3
  69. package/dist/{chunk-3E244UDA.js → chunk-QSTSJOCK.js} +52 -6
  70. package/dist/{chunk-YUPMXOGP.js → chunk-RFQMOKWK.js} +3 -3
  71. package/dist/chunk-RTOTNK3P.js +484 -0
  72. package/dist/{chunk-SN2DQRL6.js → chunk-RXKSDUAU.js} +7 -7
  73. package/dist/{chunk-T3KGZBU5.js → chunk-S45JPKQN.js} +189 -43
  74. package/dist/{chunk-YNCVSFGF.js → chunk-SAFHACFA.js} +4 -1
  75. package/dist/{chunk-KI4L2DUL.js → chunk-TCCHINEF.js} +114 -19
  76. package/dist/{chunk-AWNQ6ED6.js → chunk-THEGTRAG.js} +7 -2
  77. package/dist/{chunk-2KKODIQU.js → chunk-TR4LQE7L.js} +15 -15
  78. package/dist/{chunk-GPQ3NJPR.js → chunk-TWJMGXCI.js} +3 -3
  79. package/dist/{chunk-H72H4XDU.js → chunk-U45AXT5D.js} +9 -9
  80. package/dist/{chunk-62V2AFVG.js → chunk-U4FWBSO3.js} +160 -73
  81. package/dist/{chunk-A455ATZB.js → chunk-UBGDX6FI.js} +8 -1
  82. package/dist/chunk-UJILKYES.js +75 -0
  83. package/dist/{chunk-KSJ5UTP7.js → chunk-ULMAQV3T.js} +84 -4
  84. package/dist/{chunk-CB2HFKTP.js → chunk-VVBP6KGT.js} +1 -1
  85. package/dist/{chunk-RPOA7IAE.js → chunk-W5VITDM2.js} +43 -25
  86. package/dist/{chunk-QRTG4XTI.js → chunk-WAHQVNWT.js} +7 -1
  87. package/dist/{chunk-SQP64RCS.js → chunk-WGGYRBXH.js} +3019 -2976
  88. package/dist/{chunk-K3G6JBUC.js → chunk-WLYKU45P.js} +679 -141
  89. package/dist/chunk-WP7BVANJ.js +53 -0
  90. package/dist/{chunk-GAZEWQEZ.js → chunk-XF4VUMKW.js} +2 -2
  91. package/dist/{chunk-PAAWLLZX.js → chunk-Y5YXDM7D.js} +2 -2
  92. package/dist/{chunk-65RDIOR2.js → chunk-YOPAUVU4.js} +162 -44
  93. package/dist/{chunk-FFSX2QDQ.js → chunk-YPT4A46F.js} +3 -3
  94. package/dist/chunk-YWXT3JNM.js +425 -0
  95. package/dist/{chunk-EFEHDQGY.js → chunk-ZHZ2Y2DH.js} +64 -10
  96. package/dist/{chunk-CQQL4ZOQ.js → chunk-ZT7JNIGX.js} +4 -3
  97. package/dist/coordination/agent-monitor.d.ts +9 -1
  98. package/dist/coordination/agent-subagent-runner.d.ts +1 -1
  99. package/dist/coordination/agents/agent-prompts.d.ts +8 -0
  100. package/dist/coordination/agents/index.d.ts +33 -1
  101. package/dist/coordination/agents/index.js +52 -22
  102. package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
  103. package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
  104. package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
  105. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
  106. package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
  107. package/dist/coordination/agents/types.d.ts +13 -2
  108. package/dist/coordination/brain-monitor.d.ts +30 -7
  109. package/dist/coordination/collab-debug.d.ts +1 -1
  110. package/dist/coordination/collab-pause.d.ts +1 -1
  111. package/dist/coordination/delegate-tool.d.ts +7 -5
  112. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
  113. package/dist/coordination/director-options.d.ts +7 -0
  114. package/dist/coordination/director-spawn-model.d.ts +17 -0
  115. package/dist/coordination/director-tools.d.ts +2 -2
  116. package/dist/coordination/director.d.ts +12 -0
  117. package/dist/coordination/dispatcher.d.ts +8 -0
  118. package/dist/coordination/fleet-bus.d.ts +35 -4
  119. package/dist/coordination/fleet-supervisor.d.ts +10 -2
  120. package/dist/coordination/fleet.d.ts +16 -7
  121. package/dist/coordination/index.d.ts +4 -0
  122. package/dist/coordination/index.js +86 -49
  123. package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
  124. package/dist/coordination/mailbox-http-router.d.ts +4 -3
  125. package/dist/coordination/mailbox-http-validation.d.ts +1 -1
  126. package/dist/coordination/mailbox-project-server.js +15 -15
  127. package/dist/coordination/mailbox-types.d.ts +24 -6
  128. package/dist/coordination/model-tier-leader.d.ts +122 -0
  129. package/dist/coordination/model-tier.d.ts +122 -0
  130. package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
  131. package/dist/coordination/origin-session.d.ts +16 -0
  132. package/dist/coordination/provider-status-tracker.d.ts +37 -5
  133. package/dist/coordination/session-note-hub.d.ts +44 -0
  134. package/dist/coordination/session-note-tool.d.ts +10 -0
  135. package/dist/core/agent.d.ts +1 -1
  136. package/dist/core/context.d.ts +22 -3
  137. package/dist/core/index.d.ts +2 -0
  138. package/dist/core/index.js +130 -38
  139. package/dist/core/request-conversation-binding.d.ts +37 -0
  140. package/dist/core/session-notes.d.ts +25 -0
  141. package/dist/core/system-prompt-builder.d.ts +11 -2
  142. package/dist/core/system-prompt-variants.d.ts +78 -0
  143. package/dist/design/index.js +3 -3
  144. package/dist/execution/auto-compaction-middleware.d.ts +11 -0
  145. package/dist/execution/autonomy-brain.d.ts +2 -1
  146. package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
  147. package/dist/execution/compaction-core.d.ts +4 -4
  148. package/dist/execution/eternal-autonomy.d.ts +1 -1
  149. package/dist/execution/index.js +138 -129
  150. package/dist/execution/model-runtime.d.ts +0 -7
  151. package/dist/execution/parallel-eternal-engine.d.ts +7 -7
  152. package/dist/extension/index.js +3 -3
  153. package/dist/goal/index.js +6 -6
  154. package/dist/hooks/index.js +3 -3
  155. package/dist/hq/index.js +14 -12
  156. package/dist/hq/persistence/event-log.d.ts +0 -1
  157. package/dist/hq/persistence.d.ts +4 -4
  158. package/dist/hq/publisher.d.ts +2 -0
  159. package/dist/index.d.ts +14 -11
  160. package/dist/index.js +728 -635
  161. package/dist/infrastructure/index.d.ts +4 -4
  162. package/dist/infrastructure/index.js +281 -260
  163. package/dist/infrastructure/token-counter.d.ts +9 -0
  164. package/dist/kernel/events/agent-events.d.ts +77 -3
  165. package/dist/kernel/events/brain-events.d.ts +12 -0
  166. package/dist/kernel/events/provider-events.d.ts +7 -1
  167. package/dist/kernel/events/session-events.d.ts +14 -0
  168. package/dist/kernel/events/tool-events.d.ts +13 -0
  169. package/dist/kernel/index.js +4 -4
  170. package/dist/mailbox-attach.d.ts +1 -1
  171. package/dist/models/index.js +10 -9
  172. package/dist/observability/event-bridge.d.ts +25 -5
  173. package/dist/observability/index.d.ts +9 -8
  174. package/dist/observability/index.js +459 -351
  175. package/dist/observability/tool-usage-source.d.ts +65 -0
  176. package/dist/plugin/index.d.ts +5 -0
  177. package/dist/plugin/index.js +66 -50
  178. package/dist/plugins/auto-review-plugin.d.ts +3 -2
  179. package/dist/plugins/review-finding-commands.d.ts +0 -8
  180. package/dist/{review-finding-store-DKAV3AS6.js → plugins/review-finding-store.js} +7 -6
  181. package/dist/plugins/review-finding-types.js +19 -0
  182. package/dist/plugins/review-report-integration.js +21 -0
  183. package/dist/{review-report-store-OWY3EC3N.js → plugins/review-report-store.js} +7 -6
  184. package/dist/plugins/review-report-types.js +13 -0
  185. package/dist/prompts/index.js +5 -5
  186. package/dist/registry/index.js +3 -3
  187. package/dist/registry/tool-registry.d.ts +59 -9
  188. package/dist/{registry-R23UBE7H.js → registry-7N4O3E7W.js} +5 -5
  189. package/dist/security/capabilities.d.ts +6 -0
  190. package/dist/security/file-permissions.d.ts +1 -1
  191. package/dist/security/index.js +47 -33
  192. package/dist/security/permission-policy.d.ts +12 -1
  193. package/dist/security/secret-scrubber.d.ts +15 -0
  194. package/dist/session-catalog/index.d.ts +1 -0
  195. package/dist/session-catalog/index.js +19 -14
  196. package/dist/session-catalog/project-server.js +30 -13
  197. package/dist/session-catalog/protocol.d.ts +32 -1
  198. package/dist/session-catalog/registry.d.ts +17 -1
  199. package/dist/session-catalog/session-agents.d.ts +56 -0
  200. package/dist/session-catalog/store-rebuild.d.ts +14 -0
  201. package/dist/session-catalog/store-schema.d.ts +26 -0
  202. package/dist/session-catalog/store.d.ts +27 -1
  203. package/dist/session-note-attach.d.ts +9 -0
  204. package/dist/skills/index.js +5 -5
  205. package/dist/statusline/index.d.ts +43 -0
  206. package/dist/statusline/index.js +102 -0
  207. package/dist/storage/board-store-port.d.ts +2 -2
  208. package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
  209. package/dist/storage/config-loader.d.ts +3 -2
  210. package/dist/storage/file-session-writer.d.ts +4 -2
  211. package/dist/storage/goal-coordination.d.ts +6 -1
  212. package/dist/storage/goal-kanban.d.ts +4 -1
  213. package/dist/storage/index.d.ts +12 -4
  214. package/dist/storage/index.js +76 -61
  215. package/dist/storage/input-history-store.d.ts +2 -4
  216. package/dist/storage/memory-curator.d.ts +133 -0
  217. package/dist/storage/session-agent-attribution.d.ts +39 -0
  218. package/dist/storage/session-doctor.d.ts +109 -0
  219. package/dist/storage/session-event-bridge.d.ts +5 -7
  220. package/dist/storage/session-outcome.d.ts +42 -0
  221. package/dist/storage/session-recovery.d.ts +55 -0
  222. package/dist/storage/session-store/load-session-data.d.ts +11 -0
  223. package/dist/storage/session-store/prune-helpers.d.ts +1 -1
  224. package/dist/storage/session-store/resume-session.d.ts +0 -2
  225. package/dist/storage/session-store/types.d.ts +8 -0
  226. package/dist/storage/session-store.d.ts +3 -3
  227. package/dist/storage/session-summary-tracker.d.ts +8 -1
  228. package/dist/storage/session-write-buffer.d.ts +18 -0
  229. package/dist/storage/session-writer-truncate.d.ts +2 -0
  230. package/dist/tasking/index.js +2 -2
  231. package/dist/tools/fallback-manage-tools.d.ts +6 -4
  232. package/dist/tools/index.js +21 -21
  233. package/dist/tools/model-tier-set-tool.d.ts +66 -0
  234. package/dist/types/agent-bridge.d.ts +2 -1
  235. package/dist/types/config/mcp-features.d.ts +7 -0
  236. package/dist/types/config/model-tiers.d.ts +148 -0
  237. package/dist/types/config/root.d.ts +9 -0
  238. package/dist/types/config/tools.d.ts +73 -0
  239. package/dist/types/config.d.ts +1 -0
  240. package/dist/types/context.d.ts +2 -0
  241. package/dist/types/default-config.d.ts +6 -0
  242. package/dist/types/errors.d.ts +2 -1
  243. package/dist/types/index.d.ts +11 -9
  244. package/dist/types/index.js +25 -15
  245. package/dist/types/multi-agent.d.ts +43 -0
  246. package/dist/types/plugin.d.ts +1 -1
  247. package/dist/types/provider.d.ts +15 -0
  248. package/dist/types/runtime-capability-manifest.d.ts +1 -1
  249. package/dist/types/secret-scrubber.d.ts +15 -0
  250. package/dist/types/session-markers.d.ts +37 -10
  251. package/dist/types/session-markers.js +1 -1
  252. package/dist/types/session-timeline.d.ts +197 -0
  253. package/dist/types/session-timeline.js +12 -0
  254. package/dist/types/session.d.ts +172 -11
  255. package/dist/types/system-prompt.d.ts +27 -0
  256. package/dist/utils/config-backup.d.ts +9 -1
  257. package/dist/utils/heap-watchdog.js +2 -2
  258. package/dist/utils/index.js +50 -48
  259. package/dist/utils/message-invariants.d.ts +4 -1
  260. package/dist/utils/regex-guard.d.ts +1 -1
  261. package/dist/utils/socket-path.d.ts +1 -1
  262. package/dist/utils/token-estimate.d.ts +6 -2
  263. package/dist/utils/wstack-paths.d.ts +2 -0
  264. package/dist/wiring/proxy-rewrite.d.ts +28 -0
  265. package/dist/wiring/proxy-rewrite.js +7 -1
  266. package/dist/worktree/index.js +2 -2
  267. package/instructions/agents/explore-companion.md +45 -18
  268. package/instructions/agents/memory-curator.md +34 -22
  269. package/instructions/coordination/director-preamble.md +12 -7
  270. package/instructions/coordination/subagent-baseline.md +26 -5
  271. package/instructions/leader-after-task.md +4 -1
  272. package/instructions/llm/chimera-review.md +30 -9
  273. package/instructions/llm/memory-curator.md +31 -0
  274. package/instructions/sections/tool/delegation-compact.md +2 -0
  275. package/instructions/sections/tool/delegation-full.md +5 -1
  276. package/instructions/sections/tool/mailbox-compact.md +5 -1
  277. package/instructions/sections/tool/mailbox-full.md +3 -0
  278. package/instructions/sections/tool/session-note-compact.md +3 -0
  279. package/instructions/sections/tool/session-note-full.md +15 -0
  280. package/instructions/system-lite.md +9 -4
  281. package/instructions/system-pro.md +66 -143
  282. package/instructions/system.md +73 -175
  283. package/package.json +32 -4
  284. package/dist/chunk-2BUUIMQB.js +0 -225
  285. package/dist/chunk-FAYRX2AD.js +0 -13
  286. package/dist/chunk-H5E77KUJ.js +0 -15
  287. package/dist/chunk-M7BS2K6U.js +0 -1
  288. package/dist/coordination/agents.d.ts +0 -8
  289. package/dist/coordination/transport.d.ts +0 -2
  290. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
@@ -4,26 +4,29 @@ import {
4
4
  import {
5
5
  SessionCatalogProjectClient,
6
6
  resolveSessionCatalogProjectServerUrl
7
- } from "./chunk-52RTVH4M.js";
7
+ } from "./chunk-F3JWNST7.js";
8
8
  import {
9
- SECRET_FILE_MODE
10
- } from "./chunk-H5E77KUJ.js";
11
- import {
12
- DefaultSecretScrubber
13
- } from "./chunk-3E244UDA.js";
9
+ shouldSkipSessionDirectoryEntry
10
+ } from "./chunk-JMULFFLN.js";
14
11
  import {
15
12
  isSessionTranscriptFileName,
16
13
  sessionScopedPath
17
- } from "./chunk-AD7ZFZCS.js";
14
+ } from "./chunk-5BMIE7QG.js";
15
+ import {
16
+ DefaultSecretScrubber
17
+ } from "./chunk-QSTSJOCK.js";
18
+ import {
19
+ SECRET_FILE_MODE
20
+ } from "./chunk-6FHWXLPT.js";
18
21
  import {
19
22
  buildChildEnv
20
- } from "./chunk-VB3H5WWB.js";
23
+ } from "./chunk-6NWEVFEL.js";
21
24
  import {
22
25
  repairToolUseAdjacency
23
- } from "./chunk-M2WF6AXG.js";
26
+ } from "./chunk-LSMMOS2Z.js";
24
27
  import {
25
28
  ulid
26
- } from "./chunk-4XV6IR37.js";
29
+ } from "./chunk-HI3JGZR2.js";
27
30
  import {
28
31
  isPidAlive
29
32
  } from "./chunk-TRT2HRWZ.js";
@@ -31,7 +34,11 @@ import {
31
34
  atomicWrite,
32
35
  ensureDir,
33
36
  withFileLock
34
- } from "./chunk-7ZHZE5EO.js";
37
+ } from "./chunk-OSGLYW5J.js";
38
+ import {
39
+ effectiveInputTokens,
40
+ totalUsageTokens
41
+ } from "./chunk-3ECENVHL.js";
35
42
  import {
36
43
  toErrorMessage
37
44
  } from "./chunk-TBRL2RCC.js";
@@ -292,8 +299,8 @@ function sha256(content) {
292
299
  return createHash("sha256").update(content).digest("hex");
293
300
  }
294
301
  function isInside(root, target) {
295
- const relative3 = path.relative(root, target);
296
- return relative3 === "" || !relative3.startsWith("..") && !path.isAbsolute(relative3);
302
+ const relative4 = path.relative(root, target);
303
+ return relative4 === "" || !relative4.startsWith("..") && !path.isAbsolute(relative4);
297
304
  }
298
305
  function normalizeRelative(input) {
299
306
  if (!input || path.isAbsolute(input)) return null;
@@ -305,8 +312,8 @@ function normalizeRelative(input) {
305
312
  function parseNulPaths(output) {
306
313
  return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
307
314
  }
308
- function isWrongStackWorktreePath(relative3) {
309
- return relative3 === ".wrongstack/worktrees" || relative3.startsWith(".wrongstack/worktrees/");
315
+ function isWrongStackWorktreePath(relative4) {
316
+ return relative4 === ".wrongstack/worktrees" || relative4.startsWith(".wrongstack/worktrees/");
310
317
  }
311
318
  var SessionCheckpointCas = class {
312
319
  rootDir;
@@ -332,16 +339,16 @@ var SessionCheckpointCas = class {
332
339
  }
333
340
  const relativePaths = [
334
341
  .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
335
- ].filter((relative3) => !isWrongStackWorktreePath(relative3)).sort();
342
+ ].filter((relative4) => !isWrongStackWorktreePath(relative4)).sort();
336
343
  const unresolved = [];
337
344
  let scheduledBytes = 0;
338
345
  const captured = await mapWithConcurrency(
339
346
  relativePaths,
340
347
  CAPTURE_CONCURRENCY,
341
- async (relative3) => {
342
- const absolute = path.resolve(this.projectRoot, ...relative3.split("/"));
348
+ async (relative4) => {
349
+ const absolute = path.resolve(this.projectRoot, ...relative4.split("/"));
343
350
  if (!isInside(this.projectRoot, absolute)) {
344
- unresolved.push({ path: relative3, reason: "path escapes project root" });
351
+ unresolved.push({ path: relative4, reason: "path escapes project root" });
345
352
  return null;
346
353
  }
347
354
  try {
@@ -351,27 +358,27 @@ var SessionCheckpointCas = class {
351
358
  const resolvedLink = path.resolve(path.dirname(absolute), linkTarget);
352
359
  if (path.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
353
360
  unresolved.push({
354
- path: relative3,
361
+ path: relative4,
355
362
  reason: "symlink target escapes project root"
356
363
  });
357
364
  return null;
358
365
  }
359
- return { path: relative3, state: "symlink", linkTarget };
366
+ return { path: relative4, state: "symlink", linkTarget };
360
367
  }
361
368
  if (!stat11.isFile()) {
362
- unresolved.push({ path: relative3, reason: "changed path is not a regular file" });
369
+ unresolved.push({ path: relative4, reason: "changed path is not a regular file" });
363
370
  return null;
364
371
  }
365
372
  if (stat11.size > MAX_BLOB_BYTES) {
366
373
  unresolved.push({
367
- path: relative3,
374
+ path: relative4,
368
375
  reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
369
376
  });
370
377
  return null;
371
378
  }
372
379
  if (scheduledBytes + stat11.size > MAX_CHECKPOINT_BYTES) {
373
380
  unresolved.push({
374
- path: relative3,
381
+ path: relative4,
375
382
  reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
376
383
  });
377
384
  return null;
@@ -380,12 +387,12 @@ var SessionCheckpointCas = class {
380
387
  const content = await fsp2.readFile(absolute);
381
388
  const blobHash = sha256(content);
382
389
  await this.putBlob(blobHash, content);
383
- return { path: relative3, state: "file", blobHash, mode: stat11.mode & 511 };
390
+ return { path: relative4, state: "file", blobHash, mode: stat11.mode & 511 };
384
391
  } catch (err) {
385
392
  if (err.code === "ENOENT") {
386
- return { path: relative3, state: "absent" };
393
+ return { path: relative4, state: "absent" };
387
394
  }
388
- unresolved.push({ path: relative3, reason: toErrorMessage(err) });
395
+ unresolved.push({ path: relative4, reason: toErrorMessage(err) });
389
396
  return null;
390
397
  }
391
398
  }
@@ -412,11 +419,9 @@ var SessionCheckpointCas = class {
412
419
  }
413
420
  async materialize(checkpoint, targetRoot) {
414
421
  const target = path.resolve(targetRoot);
415
- if (target === this.projectRoot) {
416
- throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
417
- }
418
422
  const targetStat = await fsp2.stat(target);
419
- if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);
423
+ if (!targetStat.isDirectory())
424
+ throw new Error(`Checkpoint target is not a directory: ${target}`);
420
425
  const manifest = await this.loadManifest(checkpoint.manifestHash);
421
426
  if (manifest.baseHead !== checkpoint.baseHead.toLowerCase() || manifest.coverage !== checkpoint.coverage || manifest.entries.length !== checkpoint.entryCount || manifest.unresolved.length !== checkpoint.unresolvedCount) {
422
427
  throw new Error("Workspace checkpoint reference does not match its CAS manifest");
@@ -432,15 +437,18 @@ var SessionCheckpointCas = class {
432
437
  `Checkpoint target HEAD must equal ${manifest.baseHead}; got ${targetHead.stdout.trim() || "unknown"}`
433
438
  );
434
439
  }
435
- const targetStatus = await this.runGit(
436
- ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignored=matching"],
437
- target
438
- );
439
- if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {
440
- throw new Error("Unable to verify that the checkpoint target is clean");
441
- }
442
- if (targetStatus.stdout.length > 0) {
443
- throw new Error("Checkpoint target must be a clean checkout before materialization");
440
+ const materializingParentRoot = target === this.projectRoot;
441
+ if (!materializingParentRoot) {
442
+ const targetStatus = await this.runGit(
443
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignored=matching"],
444
+ target
445
+ );
446
+ if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {
447
+ throw new Error("Unable to verify that the checkpoint target is clean");
448
+ }
449
+ if (targetStatus.stdout.length > 0) {
450
+ throw new Error("Checkpoint target must be a clean checkout before materialization");
451
+ }
444
452
  }
445
453
  const realTarget = await fsp2.realpath(target);
446
454
  const writtenFiles = [];
@@ -455,7 +463,8 @@ var SessionCheckpointCas = class {
455
463
  throw new Error("absolute symlink target refused");
456
464
  }
457
465
  const resolvedLink = path.resolve(path.dirname(output), entry.linkTarget);
458
- if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
466
+ if (!isInside(target, resolvedLink))
467
+ throw new Error("symlink target escapes checkpoint root");
459
468
  }
460
469
  prepared.push({
461
470
  entry,
@@ -542,7 +551,8 @@ var SessionCheckpointCas = class {
542
551
  const target = this.manifestPath(hash);
543
552
  const existing = await fsp2.readFile(target).catch(() => null);
544
553
  if (existing) {
545
- if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);
554
+ if (sha256(existing) !== hash)
555
+ throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);
546
556
  return;
547
557
  }
548
558
  await atomicWrite(target, encoded, { mode: 384 });
@@ -554,7 +564,8 @@ var SessionCheckpointCas = class {
554
564
  }
555
565
  async loadManifest(hash) {
556
566
  const raw = await fsp2.readFile(this.manifestPath(hash));
557
- if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);
567
+ if (sha256(raw) !== hash)
568
+ throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);
558
569
  const parsed = JSON.parse(raw.toString("utf8"));
559
570
  if (parsed.version !== 1 || typeof parsed.baseHead !== "string" || parsed.coverage !== "git-head-plus-dirty" || !Array.isArray(parsed.entries) || !Array.isArray(parsed.unresolved)) {
560
571
  throw new Error(`Invalid workspace checkpoint manifest: ${hash}`);
@@ -575,8 +586,8 @@ var SessionCheckpointCas = class {
575
586
  }
576
587
  return parsed;
577
588
  }
578
- async safeOutputPath(target, realTarget, relative3) {
579
- const normalized = normalizeRelative(relative3);
589
+ async safeOutputPath(target, realTarget, relative4) {
590
+ const normalized = normalizeRelative(relative4);
580
591
  if (!normalized) throw new Error("invalid relative path");
581
592
  const output = path.resolve(target, ...normalized.split("/"));
582
593
  if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
@@ -584,7 +595,8 @@ var SessionCheckpointCas = class {
584
595
  for (; ; ) {
585
596
  try {
586
597
  const real = await fsp2.realpath(probe);
587
- if (!isInside(realTarget, real)) throw new Error("path resolves through a symlink outside checkpoint target");
598
+ if (!isInside(realTarget, real))
599
+ throw new Error("path resolves through a symlink outside checkpoint target");
588
600
  return output;
589
601
  } catch (err) {
590
602
  if (err.code !== "ENOENT") throw err;
@@ -596,7 +608,7 @@ var SessionCheckpointCas = class {
596
608
  }
597
609
  };
598
610
  function defaultRunGit(args, cwd) {
599
- return new Promise((resolve4) => {
611
+ return new Promise((resolve5) => {
600
612
  const stdoutChunks = [];
601
613
  const stderrChunks = [];
602
614
  let stdoutBytes = 0;
@@ -639,8 +651,8 @@ function defaultRunGit(args, cwd) {
639
651
  stdoutTruncated,
640
652
  stderrTruncated
641
653
  });
642
- child.on("error", (err) => resolve4(result(1, err.message)));
643
- child.on("close", (code) => resolve4(result(code ?? 1)));
654
+ child.on("error", (err) => resolve5(result(1, err.message)));
655
+ child.on("close", (code) => resolve5(result(code ?? 1)));
644
656
  });
645
657
  }
646
658
 
@@ -649,19 +661,22 @@ function sanitizeModel(model) {
649
661
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
650
662
  }
651
663
  function generateSessionId(startedAt, _model) {
652
- const date = startedAt.slice(0, 10);
653
- const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
664
+ const parsedTime = Date.parse(startedAt);
665
+ const isValidDate = !Number.isNaN(parsedTime) && /^\d{4}-\d{2}-\d{2}/.test(startedAt);
666
+ const seedTime = isValidDate ? parsedTime : Date.now();
667
+ const date = isValidDate ? startedAt.slice(0, 10) : new Date(seedTime).toISOString().slice(0, 10);
654
668
  return `${date}/sess_${ulid(seedTime)}`;
655
669
  }
656
670
 
657
671
  // src/storage/session-id-resolver.ts
658
672
  import * as path2 from "node:path";
659
673
  function resolveSessionId(query, candidateIds) {
660
- const normalized = query.trim();
674
+ const trimmed = query.trim();
675
+ const normalized = trimmed.replace(/\\/g, "/");
661
676
  if (!normalized) return { status: "missing", query: normalized };
662
- const uniqueIds = [...new Set(candidateIds)];
677
+ const uniqueIds = [...new Set(candidateIds.map((id) => id.replace(/\\/g, "/")))];
663
678
  if (uniqueIds.includes(normalized)) return { status: "resolved", id: normalized };
664
- const leaf = (id) => path2.posix.basename(id.replace(/\\/g, "/"));
679
+ const leaf = (id) => path2.posix.basename(id);
665
680
  const exactLeafMatches = uniqueIds.filter((id) => leaf(id) === normalized);
666
681
  if (exactLeafMatches.length === 1) {
667
682
  return { status: "resolved", id: exactLeafMatches[0] };
@@ -680,7 +695,7 @@ function resolveSessionId(query, candidateIds) {
680
695
  if (prefixMatches.length > 1) {
681
696
  return { status: "ambiguous", query: normalized, candidates: prefixMatches.sort() };
682
697
  }
683
- return { status: "missing", query: normalized };
698
+ return { status: "missing", query: trimmed };
684
699
  }
685
700
  function sessionIdResolutionError(resolution) {
686
701
  if (resolution.status === "ambiguous") {
@@ -752,7 +767,10 @@ function stripTerminalControls(value) {
752
767
  return out;
753
768
  }
754
769
  function sessionContentText(content) {
755
- const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
770
+ if (!content) return "";
771
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.filter(
772
+ (b) => Boolean(b && b.type === "text" && typeof b.text === "string")
773
+ ).map((b) => b.text).join(" ") : String(content);
756
774
  return stripTerminalControls(text).trim().replace(/\s+/g, " ");
757
775
  }
758
776
  function sessionContentPreview(content, maxLength = 160) {
@@ -765,7 +783,7 @@ function userInputTitle(content) {
765
783
 
766
784
  // src/storage/session-read-scrubber.ts
767
785
  function scrubPersistedSessionEvent(event, scrubber) {
768
- return scrubber.scrubObject(event);
786
+ return scrubber.scrubObjectShared ? scrubber.scrubObjectShared(event) : scrubber.scrubObject(event);
769
787
  }
770
788
  function scrubPersistedSessionData(data, scrubber) {
771
789
  return scrubber.scrubObject(data);
@@ -793,13 +811,6 @@ import { createInterface } from "node:readline";
793
811
  // src/storage/session-store/directory-session-files.ts
794
812
  import * as fsp3 from "node:fs/promises";
795
813
  import * as path3 from "node:path";
796
-
797
- // src/storage/session-store/directory-scan.ts
798
- function shouldSkipSessionDirectoryEntry(name) {
799
- return name.startsWith(".") && name !== ".wrongstack" || name === "shared" || name === "subagents" || name === "attachments";
800
- }
801
-
802
- // src/storage/session-store/directory-session-files.ts
803
814
  function sessionIdForFile(prefix, name) {
804
815
  const base = name.replace(/\.jsonl$/, "");
805
816
  return prefix ? `${prefix}/${base}` : base;
@@ -1099,6 +1110,68 @@ var SessionRecovery = class _SessionRecovery {
1099
1110
  * recent crash first.
1100
1111
  */
1101
1112
  async listResumable() {
1113
+ const out = [];
1114
+ for (const { sessionId } of await this.collectTranscripts()) {
1115
+ const stale = await this.detectStaleExact(sessionId);
1116
+ if (stale) out.push(stale);
1117
+ }
1118
+ return out.sort((a, b) => b.lastEventTs.localeCompare(a.lastEventTs));
1119
+ }
1120
+ /**
1121
+ * List every session whose journal has no trailing `session_end`, newest
1122
+ * file first. This is the read side of "recovery" as a user means it: the
1123
+ * conversations a crash, a `kill`, or a closed laptop lid left hanging.
1124
+ *
1125
+ * Ordering is by file mtime, not by boundary timestamp: a journal with no
1126
+ * lifecycle boundary at all (a session that never completed a turn) has no
1127
+ * timestamp to sort on, and mtime is the one key every candidate has.
1128
+ *
1129
+ * @param options.limit Stop after this many unclosed sessions are found.
1130
+ * Candidates are examined newest-mtime-first, so the cap keeps the common
1131
+ * "give me the last one" call to a couple of tail reads instead of a scan
1132
+ * of every transcript the project ever wrote.
1133
+ */
1134
+ async listUnclosed(options = {}) {
1135
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
1136
+ if (limit <= 0) return [];
1137
+ const candidates = await this.collectTranscripts();
1138
+ candidates.sort((a, b) => b.modifiedAt - a.modifiedAt);
1139
+ const out = [];
1140
+ for (const candidate of candidates) {
1141
+ if (out.length >= limit) break;
1142
+ const unclosed = await this.detectUnclosedExact(candidate);
1143
+ if (unclosed) out.push(unclosed);
1144
+ }
1145
+ return out;
1146
+ }
1147
+ async detectUnclosedExact(candidate) {
1148
+ if (candidate.size === 0) return null;
1149
+ let boundary;
1150
+ try {
1151
+ boundary = await scanLatestLifecycleBoundary(candidate.path, candidate.size, {
1152
+ countEvents: false
1153
+ }).then((scan) => scan?.boundary ?? null);
1154
+ } catch {
1155
+ return null;
1156
+ }
1157
+ if (boundary?.type === "session_end") return null;
1158
+ return {
1159
+ sessionId: candidate.sessionId,
1160
+ path: candidate.path,
1161
+ lastBoundary: boundary ? boundary.type : null,
1162
+ lastEventTs: boundary ? boundary.ts : new Date(candidate.modifiedAt).toISOString(),
1163
+ stale: boundary?.type === "in_flight_start",
1164
+ modifiedAt: candidate.modifiedAt
1165
+ };
1166
+ }
1167
+ /**
1168
+ * Every session transcript under `dir`, with the stat both scanners need.
1169
+ *
1170
+ * Modern sessions live inside date-shard subdirectories
1171
+ * ("2026-06-11/<base>.jsonl"); legacy/flat sessions sit at the root. Scan
1172
+ * both — a root-only scan silently misses every modern crash.
1173
+ */
1174
+ async collectTranscripts() {
1102
1175
  const out = [];
1103
1176
  const collect = async (dir, prefix, depth) => {
1104
1177
  let entries;
@@ -1120,12 +1193,18 @@ var SessionRecovery = class _SessionRecovery {
1120
1193
  if (!entry.isFile() || !isSessionTranscriptFileName(entry.name)) continue;
1121
1194
  const base = entry.name.slice(0, -".jsonl".length);
1122
1195
  const sessionId = prefix ? `${prefix}/${base}` : base;
1123
- const stale = await this.detectStaleExact(sessionId);
1124
- if (stale) out.push(stale);
1196
+ const fp = path4.join(dir, entry.name);
1197
+ let stat11;
1198
+ try {
1199
+ stat11 = await fs.stat(fp);
1200
+ } catch {
1201
+ continue;
1202
+ }
1203
+ out.push({ sessionId, path: fp, size: stat11.size, modifiedAt: stat11.mtimeMs });
1125
1204
  }
1126
1205
  };
1127
1206
  await collect(this.dir, "", 0);
1128
- return out.sort((a, b) => b.lastEventTs.localeCompare(a.lastEventTs));
1207
+ return out;
1129
1208
  }
1130
1209
  // ── Internals ──────────────────────────────────────────────────────────
1131
1210
  filePath(sessionId) {
@@ -1149,7 +1228,8 @@ function hasNonWhitespace(line) {
1149
1228
  }
1150
1229
  return false;
1151
1230
  }
1152
- async function scanLatestLifecycleBoundary(filePath, size) {
1231
+ async function scanLatestLifecycleBoundary(filePath, size, options = {}) {
1232
+ const countEvents = options.countEvents !== false;
1153
1233
  const CHUNK_SIZE2 = 64 * 1024;
1154
1234
  const handle = await fs.open(filePath, "r");
1155
1235
  let position = size;
@@ -1176,7 +1256,7 @@ async function scanLatestLifecycleBoundary(filePath, size) {
1176
1256
  lineEnd = i;
1177
1257
  }
1178
1258
  laterLineFragment = data.subarray(0, lineEnd);
1179
- if (latestBoundary && latestBoundary.type !== "in_flight_start") {
1259
+ if (latestBoundary && (!countEvents || latestBoundary.type !== "in_flight_start")) {
1180
1260
  return { boundary: latestBoundary, eventCount };
1181
1261
  }
1182
1262
  }
@@ -1201,6 +1281,19 @@ import * as path5 from "node:path";
1201
1281
  import { createReadStream as createReadStream2 } from "node:fs";
1202
1282
  import * as fsp4 from "node:fs/promises";
1203
1283
  import { createInterface as createInterface2 } from "node:readline";
1284
+
1285
+ // src/storage/session-outcome.ts
1286
+ function resolveSessionOutcome(lastEventType, hadSessionError) {
1287
+ if (lastEventType === "session_end") return "completed";
1288
+ if (lastEventType === "in_flight_start") return "aborted";
1289
+ if (hadSessionError) return "error";
1290
+ return void 0;
1291
+ }
1292
+ function isSessionErrorEvent(event) {
1293
+ return event.type === "error" || event.type === "provider_error";
1294
+ }
1295
+
1296
+ // src/storage/session-summary-tracker.ts
1204
1297
  var SessionSummaryTracker = class {
1205
1298
  id;
1206
1299
  startedAt;
@@ -1220,7 +1313,14 @@ var SessionSummaryTracker = class {
1220
1313
  messageCount = 0;
1221
1314
  lastUserMessage;
1222
1315
  lastActivityAt;
1223
- outcome = void 0;
1316
+ /**
1317
+ * Whether an `error`/`provider_error` was seen. Not a latched outcome — see
1318
+ * {@link resolveSessionOutcome} for why a trailing terminal marker outranks
1319
+ * it, and why a failed tool_result is deliberately not counted here.
1320
+ */
1321
+ hadSessionError = false;
1322
+ /** Type of the newest observed event; the terminal-marker signal. */
1323
+ lastEventType;
1224
1324
  openToolUses = /* @__PURE__ */ new Set();
1225
1325
  constructor(opts) {
1226
1326
  this.id = opts.id;
@@ -1246,7 +1346,7 @@ var SessionSummaryTracker = class {
1246
1346
  this.messageCount = this.summary.messageCount ?? 0;
1247
1347
  this.lastUserMessage = this.summary.lastUserMessage;
1248
1348
  this.lastActivityAt = this.summary.lastActivityAt ?? this.summary.endedAt ?? opts.startedAt;
1249
- this.outcome = this.resumed ? void 0 : this.summary.outcome;
1349
+ this.hadSessionError = this.resumed ? false : this.summary.outcome === "error";
1250
1350
  }
1251
1351
  get currentSummary() {
1252
1352
  return this.summary;
@@ -1271,13 +1371,17 @@ var SessionSummaryTracker = class {
1271
1371
  compactionCount: this.compactionCount > 0 ? this.compactionCount : void 0,
1272
1372
  toolBreakdown: { ...this.toolBreakdown },
1273
1373
  lastActivityAt: this.lastActivityAt,
1274
- ...this.outcome !== void 0 ? { outcome: this.outcome } : {}
1374
+ ...(() => {
1375
+ const outcome = resolveSessionOutcome(this.lastEventType, this.hadSessionError);
1376
+ return outcome !== void 0 ? { outcome } : {};
1377
+ })()
1275
1378
  };
1276
1379
  }
1277
1380
  get pendingToolUses() {
1278
1381
  return Array.from(this.openToolUses);
1279
1382
  }
1280
1383
  observe(event) {
1384
+ this.lastEventType = event.type;
1281
1385
  const eventActivityMs = Date.parse(event.ts);
1282
1386
  const lastActivityMs = Date.parse(this.lastActivityAt);
1283
1387
  if (Number.isFinite(eventActivityMs) && (!Number.isFinite(lastActivityMs) || eventActivityMs > lastActivityMs)) {
@@ -1295,17 +1399,14 @@ var SessionSummaryTracker = class {
1295
1399
  this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;
1296
1400
  } else if (event.type === "tool_result") {
1297
1401
  this.openToolUses.delete(event.id);
1298
- if (event.isError) {
1299
- this.toolErrorCount++;
1300
- this.outcome = "error";
1301
- }
1402
+ if (event.isError) this.toolErrorCount++;
1302
1403
  } else if (event.type === "file_snapshot") {
1303
1404
  this.fileChangeCount += event.files.length;
1304
1405
  } else if (event.type === "compaction") {
1305
1406
  this.compactionCount++;
1306
1407
  }
1307
- if (event.type === "error" || event.type === "provider_error") {
1308
- this.outcome = "error";
1408
+ if (isSessionErrorEvent(event)) {
1409
+ this.hadSessionError = true;
1309
1410
  }
1310
1411
  if (event.type === "user_input") {
1311
1412
  if (this.summary.title === "(empty session)") {
@@ -1315,14 +1416,21 @@ var SessionSummaryTracker = class {
1315
1416
  this.messageCount++;
1316
1417
  } else if (event.type === "llm_response") {
1317
1418
  this.messageCount++;
1318
- this.tokenIn += event.usage.input;
1419
+ this.tokenIn += effectiveInputTokens(event.usage);
1319
1420
  this.tokenOut += event.usage.output;
1320
1421
  this.summary = {
1321
1422
  ...this.summary,
1322
- tokenTotal: this.baseTokenTotal + this.tokenIn + this.tokenOut
1423
+ tokenTotal: this.baseTokenTotal + this.tokenIn + this.tokenOut,
1424
+ // Track the routed identity, not the one the session opened with. A
1425
+ // mid-session /model switch or a fallback rotation leaves the
1426
+ // create-time metadata stale, and this summary is what every session
1427
+ // listing, picker and catalog row shows. Older journals have no
1428
+ // model/provider on llm_response — those keep the create-time value.
1429
+ ...event.model !== void 0 ? { model: event.model } : {},
1430
+ ...event.provider !== void 0 ? { provider: event.provider } : {}
1323
1431
  };
1324
1432
  } else if (event.type === "session_end") {
1325
- const total = event.usage.input + event.usage.output;
1433
+ const total = totalUsageTokens(event.usage);
1326
1434
  if (total > 0) {
1327
1435
  this.summary = {
1328
1436
  ...this.summary,
@@ -1346,7 +1454,8 @@ var SessionSummaryTracker = class {
1346
1454
  this.messageCount = 0;
1347
1455
  this.lastUserMessage = void 0;
1348
1456
  this.lastActivityAt = this.startedAt;
1349
- this.outcome = void 0;
1457
+ this.hadSessionError = false;
1458
+ this.lastEventType = void 0;
1350
1459
  this.openToolUses = /* @__PURE__ */ new Set();
1351
1460
  this.tokenIn = 0;
1352
1461
  this.tokenOut = 0;
@@ -1399,7 +1508,7 @@ var SessionSummaryTracker = class {
1399
1508
  fileChangeCount: this.fileChangeCount,
1400
1509
  compactionCount: this.compactionCount > 0 ? this.compactionCount : void 0,
1401
1510
  toolBreakdown: { ...this.toolBreakdown },
1402
- outcome: this.outcome ?? "completed"
1511
+ outcome: resolveSessionOutcome(this.lastEventType, this.hadSessionError)
1403
1512
  };
1404
1513
  const resolvedName = this.resolveNameCb ? await this.resolveNameCb().catch(() => null) : null;
1405
1514
  if (resolvedName !== null) {
@@ -1425,7 +1534,8 @@ var SessionSummaryTracker = class {
1425
1534
  this.messageCount = 0;
1426
1535
  this.lastUserMessage = void 0;
1427
1536
  this.lastActivityAt = resetAt;
1428
- this.outcome = void 0;
1537
+ this.hadSessionError = false;
1538
+ this.lastEventType = void 0;
1429
1539
  this.openToolUses = /* @__PURE__ */ new Set();
1430
1540
  this.summary = {
1431
1541
  id: this.id,
@@ -1457,6 +1567,16 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1457
1567
  static FLUSH_SIZE = 50;
1458
1568
  static WRITE_BUFFER_MAX_EVENTS = 2e3;
1459
1569
  static WRITE_BUFFER_MAX_BYTES = 16 * 1024 * 1024;
1570
+ /**
1571
+ * Absolute ceiling for ONE event, even via the empty-buffer exemption in
1572
+ * `push`. That exemption exists so a legitimately huge user prompt is
1573
+ * persisted instead of silently dropped (its bytes are already resident —
1574
+ * accepting it costs one reference, not a copy); without a ceiling an
1575
+ * unbounded payload could pin memory and stall the event loop on
1576
+ * stringify. 64MiB is 4x the retention budget and far above any real
1577
+ * prompt, so it only fires on pathological input.
1578
+ */
1579
+ static MAX_SINGLE_EVENT_BYTES = 64 * 1024 * 1024;
1460
1580
  writeBuffer = [];
1461
1581
  writeBufferBytes = 0;
1462
1582
  flushTimer = null;
@@ -1464,6 +1584,8 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1464
1584
  lastBufferOverflowWarnAt = 0;
1465
1585
  appendFailCount = 0;
1466
1586
  lastAppendWarnAt = 0;
1587
+ invalidEventCount = 0;
1588
+ lastInvalidEventWarnAt = 0;
1467
1589
  writeChain = Promise.resolve();
1468
1590
  flushPromise = null;
1469
1591
  /**
@@ -1482,12 +1604,31 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1482
1604
  try {
1483
1605
  return Buffer.byteLength(JSON.stringify(event), "utf8") + 1;
1484
1606
  } catch {
1485
- return _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES + 1;
1607
+ return Number.NaN;
1486
1608
  }
1487
1609
  }
1488
1610
  push(event) {
1489
1611
  const bytes = this.eventBytes(event);
1490
- if (this.writeBuffer.length >= _SessionWriteBuffer.WRITE_BUFFER_MAX_EVENTS || bytes > _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES || this.writeBufferBytes + bytes > _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES) {
1612
+ if (!Number.isFinite(bytes)) {
1613
+ this.invalidEventCount++;
1614
+ const now = Date.now();
1615
+ if (now - this.lastInvalidEventWarnAt > 5e3) {
1616
+ this.lastInvalidEventWarnAt = now;
1617
+ console.warn(
1618
+ JSON.stringify({
1619
+ level: "error",
1620
+ event: "session.buffer_push_invalid",
1621
+ sessionId: this.opts.sessionId,
1622
+ reason: "event is not JSON-serializable",
1623
+ droppedEvents: this.invalidEventCount,
1624
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1625
+ })
1626
+ );
1627
+ }
1628
+ return false;
1629
+ }
1630
+ const oversizedSingleEvent = bytes > _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES && bytes <= _SessionWriteBuffer.MAX_SINGLE_EVENT_BYTES && this.writeBuffer.length === 0;
1631
+ if (this.writeBuffer.length >= _SessionWriteBuffer.WRITE_BUFFER_MAX_EVENTS || !oversizedSingleEvent && (bytes > _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES || this.writeBufferBytes + bytes > _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES)) {
1491
1632
  this.bufferOverflowCount++;
1492
1633
  const now = Date.now();
1493
1634
  if (now - this.lastBufferOverflowWarnAt > 5e3) {
@@ -1520,7 +1661,7 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1520
1661
  flight.started = true;
1521
1662
  if (flight.stolen) return;
1522
1663
  try {
1523
- return await this.opts.getHandle().appendFile(flight.data, "utf8");
1664
+ await this.opts.getHandle().appendFile(flight.data, "utf8");
1524
1665
  } catch (err) {
1525
1666
  if (isClosedHandleError(err)) {
1526
1667
  const reloaded = await fsp5.open(this.opts.filePath, "a", 384);
@@ -1644,6 +1785,12 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1644
1785
  /**
1645
1786
  * Last-gasp synchronous append (SIGKILL/SIGTERM traps, `process.on('exit')`).
1646
1787
  *
1788
+ * If an async append is actively writing when teardown arrives, flushSync
1789
+ * must not open a second append descriptor and race it. It defers with a
1790
+ * structured `session.flush_sync_deferred` warning, leaving the buffer
1791
+ * untouched for the already-running flush loop to drain if the process
1792
+ * survives.
1793
+ *
1647
1794
  * Failure contract: nothing is discarded before the write is known to have
1648
1795
  * landed. Buffered events stay in `writeBuffer` — it is cleared only after
1649
1796
  * `fsyncSync` returns — and a stolen in-flight batch is handed back to the
@@ -1658,6 +1805,21 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1658
1805
  const chunks = [];
1659
1806
  const flight = this.inFlight;
1660
1807
  const stole = flight !== null && !flight.started && !flight.stolen;
1808
+ if (flight !== null && flight.started && !flight.stolen) {
1809
+ console.warn(
1810
+ JSON.stringify({
1811
+ level: "error",
1812
+ event: "session.flush_sync_deferred",
1813
+ sessionId: this.opts.sessionId,
1814
+ filePath: this.opts.filePath,
1815
+ message: "in-flight async append already started; sync append deferred to avoid write race",
1816
+ pendingEvents: this.writeBuffer.length,
1817
+ pendingBytes: this.writeBufferBytes,
1818
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1819
+ })
1820
+ );
1821
+ return;
1822
+ }
1661
1823
  if (stole && flight) {
1662
1824
  flight.stolen = true;
1663
1825
  chunks.push(flight.data);
@@ -1710,7 +1872,7 @@ var SessionWriteBuffer = class _SessionWriteBuffer {
1710
1872
  this.lastBufferOverflowWarnAt = 0;
1711
1873
  }
1712
1874
  shouldFlushNow() {
1713
- return this.writeBuffer.length >= _SessionWriteBuffer.FLUSH_SIZE;
1875
+ return this.writeBuffer.length >= _SessionWriteBuffer.FLUSH_SIZE || this.writeBufferBytes >= _SessionWriteBuffer.WRITE_BUFFER_MAX_BYTES;
1714
1876
  }
1715
1877
  };
1716
1878
 
@@ -1739,6 +1901,18 @@ function scrubSessionWriterEvent(event, secretScrubber) {
1739
1901
  if (event.type === "llm_response") {
1740
1902
  return { ...event, content: secretScrubber.scrubObject(event.content) };
1741
1903
  }
1904
+ if (event.type === "tool_use") {
1905
+ return { ...event, input: secretScrubber.scrubObject(event.input) };
1906
+ }
1907
+ if (event.type === "tool_call_start") {
1908
+ return { ...event, input: secretScrubber.scrubObject(event.input) };
1909
+ }
1910
+ if (event.type === "tool_result") {
1911
+ return {
1912
+ ...event,
1913
+ content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
1914
+ };
1915
+ }
1742
1916
  if (event.type === "file_snapshot") {
1743
1917
  return {
1744
1918
  ...event,
@@ -1749,6 +1923,9 @@ function scrubSessionWriterEvent(event, secretScrubber) {
1749
1923
  }))
1750
1924
  };
1751
1925
  }
1926
+ if (event.type === "side_effect") {
1927
+ return secretScrubber.scrubObject(event);
1928
+ }
1752
1929
  return event;
1753
1930
  }
1754
1931
 
@@ -1761,6 +1938,8 @@ async function findSessionCheckpointTruncatePlan(filePath, targetPromptIndex) {
1761
1938
  let pendingLine = Buffer.alloc(0);
1762
1939
  let checkpointByteOffset = -1;
1763
1940
  let removedCount = 0;
1941
+ const removedSubagentTranscriptPaths = /* @__PURE__ */ new Set();
1942
+ const retainedSubagentTranscriptPaths = /* @__PURE__ */ new Set();
1764
1943
  try {
1765
1944
  fd = await fsp6.open(filePath, "r", 384);
1766
1945
  while (true) {
@@ -1775,15 +1954,20 @@ async function findSessionCheckpointTruncatePlan(filePath, targetPromptIndex) {
1775
1954
  const idx = chunk.indexOf("\n", chunkPos);
1776
1955
  if (idx === -1) break;
1777
1956
  const lineStartOffset = chunkFileOffset + chunkPos;
1957
+ const lineBytes = chunk.subarray(chunkPos, idx);
1778
1958
  if (checkpointByteOffset !== -1) {
1779
1959
  removedCount++;
1960
+ collectSubagentTranscriptPath(lineBytes, removedSubagentTranscriptPaths);
1780
1961
  } else {
1781
- const lineBytes = chunk.subarray(chunkPos, idx);
1782
1962
  const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
1783
1963
  if (line.trim()) {
1784
1964
  try {
1785
1965
  const event = JSON.parse(line);
1786
- if (event.type === "checkpoint") {
1966
+ if (event.type === "agent_session_linked") {
1967
+ if (typeof event.transcriptPath === "string" && event.transcriptPath.trim()) {
1968
+ retainedSubagentTranscriptPaths.add(event.transcriptPath);
1969
+ }
1970
+ } else if (event.type === "checkpoint") {
1787
1971
  if (event.promptIndex === targetPromptIndex) {
1788
1972
  checkpointByteOffset = lineStartOffset;
1789
1973
  } else if (event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
@@ -1803,7 +1987,23 @@ async function findSessionCheckpointTruncatePlan(filePath, targetPromptIndex) {
1803
1987
  await fd?.close();
1804
1988
  }
1805
1989
  if (checkpointByteOffset === -1) return null;
1806
- return { checkpointByteOffset, removedCount };
1990
+ return {
1991
+ checkpointByteOffset,
1992
+ removedCount,
1993
+ removedSubagentTranscriptPaths: [...removedSubagentTranscriptPaths].filter((transcriptPath) => !retainedSubagentTranscriptPaths.has(transcriptPath)).sort()
1994
+ };
1995
+ }
1996
+ function collectSubagentTranscriptPath(lineBytes, out) {
1997
+ const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
1998
+ if (!line.trim()) return;
1999
+ try {
2000
+ const event = JSON.parse(line);
2001
+ if (event.type !== "agent_session_linked") return;
2002
+ if (typeof event.transcriptPath === "string" && event.transcriptPath.trim()) {
2003
+ out.add(event.transcriptPath);
2004
+ }
2005
+ } catch {
2006
+ }
1807
2007
  }
1808
2008
  async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
1809
2009
  const tmpPath = `${filePath}.rewind.tmp`;
@@ -1862,6 +2062,7 @@ var CRITICAL_EVENT_TYPES = /* @__PURE__ */ new Set([
1862
2062
  "user_input",
1863
2063
  "llm_response",
1864
2064
  "checkpoint",
2065
+ "rewound",
1865
2066
  "in_flight_start",
1866
2067
  "in_flight_end"
1867
2068
  ]);
@@ -1877,6 +2078,7 @@ var FileSessionWriter = class _FileSessionWriter {
1877
2078
  this.meta = meta;
1878
2079
  this.events = events;
1879
2080
  this.resumed = opts.resumed ?? false;
2081
+ this.lifecyclePreambleTs = this.resumed ? (/* @__PURE__ */ new Date()).toISOString() : startedAt;
1880
2082
  this.manifestFile = opts.dir ? path5.join(opts.dir, `${path5.basename(id)}.summary.json`) : "";
1881
2083
  this.filePath = opts.filePath ?? "";
1882
2084
  this.secretScrubber = opts.secretScrubber;
@@ -1932,6 +2134,7 @@ var FileSessionWriter = class _FileSessionWriter {
1932
2134
  return this.initPromise;
1933
2135
  }
1934
2136
  resumed;
2137
+ lifecyclePreambleTs;
1935
2138
  secretScrubber;
1936
2139
  checkpointCas;
1937
2140
  /** Mutable slot for onAppend callback — constructor opts seed it, setOnAppend replaces it. */
@@ -2013,7 +2216,18 @@ var FileSessionWriter = class _FileSessionWriter {
2013
2216
  `)).then(() => {
2014
2217
  if (!critical) return;
2015
2218
  return this.handle.datasync().catch(() => void 0);
2016
- }).catch(() => void 0);
2219
+ }).catch((err) => {
2220
+ console.warn(
2221
+ JSON.stringify({
2222
+ level: "error",
2223
+ event: "session.sync_journal_write_failed",
2224
+ sessionId: this.id,
2225
+ eventType: appendEvent.type,
2226
+ message: toErrorMessage(err),
2227
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2228
+ })
2229
+ );
2230
+ });
2017
2231
  });
2018
2232
  } else if (critical || this.buffer.shouldFlushNow()) {
2019
2233
  this.buffer.cancelTimer();
@@ -2082,7 +2296,7 @@ var FileSessionWriter = class _FileSessionWriter {
2082
2296
  async writeSessionStartLazy() {
2083
2297
  this.buffer.push({
2084
2298
  type: this.resumed ? "session_resumed" : "session_start",
2085
- ts: this.startedAt,
2299
+ ts: this.lifecyclePreambleTs,
2086
2300
  id: this.id,
2087
2301
  model: this.meta.model ?? "unknown",
2088
2302
  provider: this.meta.provider ?? "unknown"
@@ -2188,7 +2402,7 @@ var FileSessionWriter = class _FileSessionWriter {
2188
2402
  `)).then(() => {
2189
2403
  if (!isCriticalEvent(scrubbed)) return;
2190
2404
  return this.handle.datasync().catch(() => void 0);
2191
- }).catch(() => void 0);
2405
+ });
2192
2406
  }
2193
2407
  }
2194
2408
  if (isCriticalEvent(scrubbed) || this.buffer.shouldFlushNow()) {
@@ -2220,7 +2434,7 @@ var FileSessionWriter = class _FileSessionWriter {
2220
2434
  `)).then(() => {
2221
2435
  if (!isCriticalEvent(scrubbed)) return;
2222
2436
  return this.handle.datasync().catch(() => void 0);
2223
- }).catch(() => void 0);
2437
+ });
2224
2438
  }
2225
2439
  }
2226
2440
  scrubbedBatch.push(scrubbed);
@@ -2453,6 +2667,7 @@ var FileSessionWriter = class _FileSessionWriter {
2453
2667
  }
2454
2668
  try {
2455
2669
  await rewriteSessionToCheckpoint(this.filePath, plan.checkpointByteOffset);
2670
+ await this.deleteRewoundSubagentTranscripts(plan.removedSubagentTranscriptPaths);
2456
2671
  this.handle = await fsp7.open(this.filePath, "a", 384);
2457
2672
  } catch (err) {
2458
2673
  this.handle = await fsp7.open(this.filePath, "a", 384).catch(() => this.handle);
@@ -2476,6 +2691,42 @@ var FileSessionWriter = class _FileSessionWriter {
2476
2691
  });
2477
2692
  return plan.removedCount;
2478
2693
  }
2694
+ async deleteRewoundSubagentTranscripts(transcriptPaths) {
2695
+ if (transcriptPaths.length === 0) return;
2696
+ const sessionsRoot = this.id.includes("/") ? path5.dirname(path5.dirname(this.filePath)) : path5.dirname(this.filePath);
2697
+ const allowedRoot = path5.join(sessionsRoot, "subagents");
2698
+ const realAllowedRoot = await fsp7.realpath(allowedRoot).catch(() => null);
2699
+ if (!realAllowedRoot) return;
2700
+ const deleted = [];
2701
+ for (const transcriptPath of transcriptPaths) {
2702
+ const resolved = path5.resolve(transcriptPath);
2703
+ const relative4 = path5.relative(realAllowedRoot, resolved);
2704
+ if (relative4.startsWith("..") || path5.isAbsolute(relative4)) continue;
2705
+ await fsp7.rm(resolved, { force: true }).then(
2706
+ () => {
2707
+ deleted.push(resolved);
2708
+ },
2709
+ (err) => {
2710
+ console.warn(
2711
+ JSON.stringify({
2712
+ level: "warn",
2713
+ event: "session.rewind_subagent_delete_failed",
2714
+ sessionId: this.id,
2715
+ filePath: resolved,
2716
+ error: toErrorMessage(err),
2717
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2718
+ })
2719
+ );
2720
+ }
2721
+ );
2722
+ }
2723
+ if (deleted.length > 0) {
2724
+ this.events?.emit("session.rewind_subagents_deleted", {
2725
+ sessionId: this.id,
2726
+ transcriptPaths: deleted
2727
+ });
2728
+ }
2729
+ }
2479
2730
  async clearSession() {
2480
2731
  if (!this.filePath) return;
2481
2732
  this.buffer.cancelTimer();
@@ -2588,7 +2839,8 @@ async function deleteSessionArtifacts({
2588
2839
  fsp8.unlink(sessionPath(rootDir, id, ".summary.json")),
2589
2840
  fsp8.unlink(sessionPath(rootDir, id, ".plan.json")),
2590
2841
  fsp8.unlink(sessionPath(rootDir, id, ".tasks.json")),
2591
- fsp8.unlink(sessionPath(rootDir, id, ".todos.json"))
2842
+ fsp8.unlink(sessionPath(rootDir, id, ".todos.json")),
2843
+ fsp8.unlink(shardManifestPath(rootDir, path7.dirname(id) === "." ? "" : path7.dirname(id)))
2592
2844
  ];
2593
2845
  const results = await Promise.allSettled(deletions);
2594
2846
  for (const result of results) {
@@ -2708,6 +2960,12 @@ function inheritsIntoFork(event) {
2708
2960
  case "in_flight_start":
2709
2961
  case "in_flight_end":
2710
2962
  case "rewound":
2963
+ case "agent_spawned":
2964
+ case "agent_session_linked":
2965
+ case "agent_stopped":
2966
+ case "agent_error":
2967
+ case "delegate_started":
2968
+ case "delegate_completed":
2711
2969
  return false;
2712
2970
  // Parent snapshots describe mutations owned by the parent journal. A
2713
2971
  // child that shares the current workspace must not inherit authority to
@@ -2786,6 +3044,25 @@ async function forkSession(host, id, opts = {}) {
2786
3044
  // src/storage/session-store/load-cache.ts
2787
3045
  var LOAD_CACHE_MAX_ENTRIES = 50;
2788
3046
  var LOAD_CACHE_MAX_BYTES = 64 * 1024 * 1024;
3047
+ var SIZE_WALK_MAX_DEPTH = 12;
3048
+ function approximateRetainedBytes(value, depth = 0) {
3049
+ if (value === null || value === void 0) return 0;
3050
+ const type = typeof value;
3051
+ if (type === "string") return value.length * 2;
3052
+ if (type === "number" || type === "boolean") return 8;
3053
+ if (type !== "object") return 0;
3054
+ if (depth >= SIZE_WALK_MAX_DEPTH) return 0;
3055
+ if (Array.isArray(value)) {
3056
+ let bytes2 = 32;
3057
+ for (const item of value) bytes2 += approximateRetainedBytes(item, depth + 1);
3058
+ return bytes2;
3059
+ }
3060
+ let bytes = 48;
3061
+ for (const [key, child] of Object.entries(value)) {
3062
+ bytes += key.length * 2 + approximateRetainedBytes(child, depth + 1);
3063
+ }
3064
+ return bytes;
3065
+ }
2789
3066
  var SessionLoadCache = class {
2790
3067
  constructor(entries = /* @__PURE__ */ new Map()) {
2791
3068
  this.entries = entries;
@@ -2826,25 +3103,27 @@ var SessionLoadCache = class {
2826
3103
  }
2827
3104
  set(id, stat11, data) {
2828
3105
  this.delete(id);
2829
- while (stat11.size <= LOAD_CACHE_MAX_BYTES && (this.entries.size >= LOAD_CACHE_MAX_ENTRIES || this.bytes + stat11.size > LOAD_CACHE_MAX_BYTES)) {
3106
+ const cost = approximateRetainedBytes(data);
3107
+ while (cost <= LOAD_CACHE_MAX_BYTES && (this.entries.size >= LOAD_CACHE_MAX_ENTRIES || this.bytes + cost > LOAD_CACHE_MAX_BYTES)) {
2830
3108
  const oldest = this.entries.keys().next().value;
2831
3109
  if (oldest === void 0) break;
2832
3110
  this.delete(oldest);
2833
3111
  }
2834
- if (stat11.size <= LOAD_CACHE_MAX_BYTES) {
2835
- this.entries.set(id, { mtimeMs: stat11.mtimeMs, size: stat11.size, data });
2836
- this.bytes += stat11.size;
3112
+ if (cost <= LOAD_CACHE_MAX_BYTES) {
3113
+ this.entries.set(id, { mtimeMs: stat11.mtimeMs, size: stat11.size, data, bytes: cost });
3114
+ this.bytes += cost;
2837
3115
  }
2838
3116
  }
2839
3117
  delete(sessionId) {
2840
3118
  const cached = this.entries.get(sessionId);
2841
- if (cached) this.bytes = Math.max(0, this.bytes - cached.size);
3119
+ if (cached) this.bytes = Math.max(0, this.bytes - (cached.bytes ?? cached.size));
2842
3120
  this.entries.delete(sessionId);
2843
3121
  }
2844
3122
  };
2845
3123
 
2846
3124
  // src/storage/session-store/load-session-data.ts
2847
3125
  import { createReadStream as createReadStream3 } from "node:fs";
3126
+ import { stat as fspStat } from "node:fs/promises";
2848
3127
  import { createInterface as createInterface3 } from "node:readline";
2849
3128
 
2850
3129
  // src/storage/session-tool-call-ends.ts
@@ -2869,6 +3148,7 @@ function extractToolCallEnds(events) {
2869
3148
  var DEFAULT_MAX_RETAINED_EVENT_BYTES = 96 * 1024 * 1024;
2870
3149
  var EVICT_TO_FRACTION = 0.9;
2871
3150
  var SNAPSHOT_ENVELOPE_BYTES = 256;
3151
+ var LOAD_PROGRESS_INTERVAL_MS = 250;
2872
3152
  async function loadSessionDataFromFile(params) {
2873
3153
  const events = [];
2874
3154
  const eventBytes = [];
@@ -2886,12 +3166,24 @@ async function loadSessionDataFromFile(params) {
2886
3166
  const messages = params.full ? [] : void 0;
2887
3167
  const openToolUses = params.full ? /* @__PURE__ */ new Set() : void 0;
2888
3168
  let exactJournalActive = false;
3169
+ let messageIndexOffset = 0;
2889
3170
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2890
3171
  let lastSnapshot;
3172
+ const totalBytes = params.onLoadProgress ? (await fspStat(params.file)).size : 0;
2891
3173
  const stream = createReadStream3(params.file, { encoding: "utf8" });
2892
3174
  const rl = createInterface3({ input: stream, crlfDelay: Infinity });
3175
+ let loadedBytes = 0;
3176
+ let lastProgressAt = 0;
3177
+ const progressIntervalMs = params.progressIntervalMs ?? LOAD_PROGRESS_INTERVAL_MS;
2893
3178
  try {
2894
3179
  for await (const line of rl) {
3180
+ if (params.onLoadProgress) {
3181
+ loadedBytes += Buffer.byteLength(line, "utf8") + 1;
3182
+ if (Date.now() - lastProgressAt >= progressIntervalMs) {
3183
+ lastProgressAt = Date.now();
3184
+ params.onLoadProgress({ loadedBytes, totalBytes });
3185
+ }
3186
+ }
2895
3187
  if (!line.trim()) continue;
2896
3188
  try {
2897
3189
  const parsed = JSON.parse(line);
@@ -2945,9 +3237,11 @@ async function loadSessionDataFromFile(params) {
2945
3237
  messages,
2946
3238
  openToolUses,
2947
3239
  exactJournalActive,
3240
+ messageIndexOffset,
2948
3241
  usage
2949
3242
  });
2950
3243
  exactJournalActive = replayState.exactJournalActive;
3244
+ messageIndexOffset = replayState.messageIndexOffset;
2951
3245
  usage = replayState.usage ?? usage;
2952
3246
  } else if (ev.type === "llm_response") {
2953
3247
  usage = accumulateUsage(usage, ev);
@@ -2955,6 +3249,9 @@ async function loadSessionDataFromFile(params) {
2955
3249
  } catch {
2956
3250
  }
2957
3251
  }
3252
+ if (params.onLoadProgress) {
3253
+ params.onLoadProgress({ loadedBytes: totalBytes, totalBytes });
3254
+ }
2958
3255
  } finally {
2959
3256
  rl.close();
2960
3257
  stream.close();
@@ -2967,7 +3264,7 @@ async function loadSessionDataFromFile(params) {
2967
3264
  detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
2968
3265
  });
2969
3266
  }
2970
- const repaired = repairToolUseAdjacency(messages);
3267
+ const repaired = repairToolUseAdjacency(messages, { preserveTrailingToolUse: true });
2971
3268
  if (repaired.report.changed) {
2972
3269
  params.events?.emit("session.damaged", {
2973
3270
  sessionId: params.id,
@@ -3020,14 +3317,16 @@ function isSessionEventLike(value) {
3020
3317
  function replaySessionEvent(params) {
3021
3318
  const { ev, messages, openToolUses } = params;
3022
3319
  let exactJournalActive = params.exactJournalActive;
3320
+ let messageIndexOffset = params.messageIndexOffset;
3023
3321
  if (ev.type === "message_appended" && ev.version === 1) {
3024
3322
  const message = replayableMessage(ev.message, ev.ts);
3025
3323
  if (message) {
3026
- if (!exactJournalActive) {
3324
+ if (!exactJournalActive && shouldReplaceLegacyReplay(messages, ev.ts)) {
3027
3325
  messages.length = 0;
3028
3326
  openToolUses.clear();
3029
- exactJournalActive = true;
3327
+ messageIndexOffset = 0;
3030
3328
  }
3329
+ exactJournalActive = true;
3031
3330
  messages.push(message);
3032
3331
  trackMessageToolState(message, openToolUses);
3033
3332
  } else {
@@ -3035,8 +3334,9 @@ function replaySessionEvent(params) {
3035
3334
  }
3036
3335
  } else if (ev.type === "message_updated" && ev.version === 1) {
3037
3336
  const message = replayableMessage(ev.message, ev.ts);
3038
- if (message && exactJournalActive && ev.index >= 0 && ev.index < messages.length) {
3039
- messages[ev.index] = message;
3337
+ const index = ev.index - messageIndexOffset;
3338
+ if (message && exactJournalActive && index >= 0 && index < messages.length) {
3339
+ messages[index] = message;
3040
3340
  openToolUses.clear();
3041
3341
  for (const current of messages) trackMessageToolState(current, openToolUses);
3042
3342
  } else {
@@ -3050,12 +3350,15 @@ function replaySessionEvent(params) {
3050
3350
  );
3051
3351
  } else if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
3052
3352
  exactJournalActive = true;
3353
+ messageIndexOffset = 0;
3053
3354
  } else {
3054
3355
  emitDamaged(params, "Ignored malformed messages_replaced event");
3055
3356
  }
3056
3357
  } else if (ev.type === "messages_dropped" && ev.version === 1) {
3057
3358
  if (exactJournalActive && Number.isInteger(ev.count) && ev.count > 0) {
3058
- messages.splice(0, Math.min(ev.count, messages.length));
3359
+ const dropped = Math.min(ev.count, messages.length);
3360
+ messages.splice(0, dropped);
3361
+ messageIndexOffset += dropped;
3059
3362
  openToolUses.clear();
3060
3363
  for (const current of messages) trackMessageToolState(current, openToolUses);
3061
3364
  } else if (!exactJournalActive) {
@@ -3069,7 +3372,9 @@ function replaySessionEvent(params) {
3069
3372
  params,
3070
3373
  `Ignored context_snapshot event whose payload was stripped before persistence (${String(ev.messagesOmitted)} messages)`
3071
3374
  );
3072
- } else if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
3375
+ } else if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
3376
+ messageIndexOffset = 0;
3377
+ } else {
3073
3378
  emitDamaged(params, "Ignored malformed context_snapshot event");
3074
3379
  }
3075
3380
  } else if (ev.type === "messages_replaced" || ev.type === "message_appended" || ev.type === "message_updated" || ev.type === "messages_dropped") {
@@ -3084,11 +3389,11 @@ function replaySessionEvent(params) {
3084
3389
  if (b.type === "tool_use") openToolUses.add(b.id);
3085
3390
  }
3086
3391
  }
3087
- return { exactJournalActive, usage: accumulateUsage(params.usage, ev) };
3392
+ return { exactJournalActive, messageIndexOffset, usage: accumulateUsage(params.usage, ev) };
3088
3393
  } else if (!exactJournalActive && ev.type === "tool_result") {
3089
3394
  if (!openToolUses.has(ev.id)) {
3090
3395
  emitDamaged(params, `Orphan tool_result "${ev.id}" has no matching tool_use`);
3091
- return { exactJournalActive };
3396
+ return { exactJournalActive, messageIndexOffset };
3092
3397
  }
3093
3398
  openToolUses.delete(ev.id);
3094
3399
  const resultBlock = {
@@ -3105,7 +3410,12 @@ function replaySessionEvent(params) {
3105
3410
  messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
3106
3411
  }
3107
3412
  }
3108
- return { exactJournalActive };
3413
+ return { exactJournalActive, messageIndexOffset };
3414
+ }
3415
+ function shouldReplaceLegacyReplay(messages, eventTs) {
3416
+ if (messages.length === 0) return true;
3417
+ const lastTs = messages[messages.length - 1]?.ts;
3418
+ return typeof lastTs === "string" && lastTs >= eventTs;
3109
3419
  }
3110
3420
  function accumulateUsage(usage, ev) {
3111
3421
  return {
@@ -3128,7 +3438,7 @@ import * as path8 from "node:path";
3128
3438
  function isPrunableSessionJsonl(name) {
3129
3439
  return isSessionTranscriptFileName(name);
3130
3440
  }
3131
- async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
3441
+ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession, isSessionInUse) {
3132
3442
  const cutoff = Date.now() - maxAgeDays * 864e5;
3133
3443
  let deleted = 0;
3134
3444
  const pruneFile = async (dir, name, prefix) => {
@@ -3141,6 +3451,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
3141
3451
  }
3142
3452
  const base = name.replace(/\.jsonl$/, "");
3143
3453
  const id = prefix ? `${prefix}/${base}` : base;
3454
+ if (isSessionInUse && await isSessionInUse(id)) return;
3144
3455
  await deleteSession(id);
3145
3456
  deleted++;
3146
3457
  };
@@ -3246,8 +3557,8 @@ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
3246
3557
  var VALIDATION_CONCURRENCY = 8;
3247
3558
  var NOTICE_PATH_LIMIT = 20;
3248
3559
  function isInside2(root, target) {
3249
- const relative3 = path9.relative(root, target);
3250
- return relative3 === "" || !relative3.startsWith("..") && !path9.isAbsolute(relative3);
3560
+ const relative4 = path9.relative(root, target);
3561
+ return relative4 === "" || !relative4.startsWith("..") && !path9.isAbsolute(relative4);
3251
3562
  }
3252
3563
  function errno(err) {
3253
3564
  return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
@@ -3357,8 +3668,8 @@ function formatResumeValidationNotice(validation, projectRoot) {
3357
3668
  if (validation.staleFiles.length === 0) return null;
3358
3669
  const root = path9.resolve(projectRoot);
3359
3670
  const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
3360
- const relative3 = path9.relative(root, entry.path);
3361
- const display = isInside2(root, entry.path) ? relative3 || "." : entry.path;
3671
+ const relative4 = path9.relative(root, entry.path);
3672
+ const display = isInside2(root, entry.path) ? relative4 || "." : entry.path;
3362
3673
  return `- ${JSON.stringify(display)} [${entry.status}]`;
3363
3674
  });
3364
3675
  const omitted = validation.staleFiles.length - shown.length;
@@ -3420,7 +3731,6 @@ async function summarizeSessionEventSequence(opts) {
3420
3731
  }
3421
3732
  }
3422
3733
  const toolBreakdown = {};
3423
- let outcome;
3424
3734
  let lastEventType;
3425
3735
  let hasError = false;
3426
3736
  let sawStart = false;
@@ -3467,23 +3777,21 @@ async function summarizeSessionEventSequence(opts) {
3467
3777
  }
3468
3778
  } else if (e.type === "llm_response") {
3469
3779
  messageCount++;
3470
- tokenIn += e.usage.input ?? 0;
3471
- tokenOut += e.usage.output ?? 0;
3780
+ if (e.usage) {
3781
+ tokenIn += effectiveInputTokens(e.usage);
3782
+ tokenOut += e.usage.output ?? 0;
3783
+ }
3784
+ if (e.model) model = e.model;
3785
+ if (e.provider) provider = e.provider;
3472
3786
  } else if (e.type === "in_flight_start") iterationCount++;
3473
3787
  else if (e.type === "tool_call_start") {
3474
3788
  toolCallCount++;
3475
3789
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
3476
3790
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
3477
3791
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
3478
- else if (e.type === "error" || e.type === "provider_error") hasError = true;
3479
- }
3480
- if (lastEventType === "session_end") {
3481
- outcome = "completed";
3482
- } else if (lastEventType === "in_flight_start") {
3483
- outcome = "aborted";
3484
- } else if (hasError) {
3485
- outcome = "error";
3792
+ else if (isSessionErrorEvent(e)) hasError = true;
3486
3793
  }
3794
+ const outcome = resolveSessionOutcome(lastEventType, hasError);
3487
3795
  return {
3488
3796
  id,
3489
3797
  title,
@@ -3594,10 +3902,7 @@ async function executeResumeSession(params) {
3594
3902
  let resumeValidation;
3595
3903
  if (projectRoot) {
3596
3904
  try {
3597
- const validationEvents = eventsDropped > 0 ? (await searchEvents(
3598
- canonicalId,
3599
- (ev) => ev.type === "file_observation"
3600
- )).map((h) => h.event) : data.events;
3905
+ const validationEvents = eventsDropped > 0 ? (await searchEvents(canonicalId, (ev) => ev.type === "file_observation")).map((h) => h.event) : data.events;
3601
3906
  resumeValidation = await validateResumeFileObservations(validationEvents, projectRoot);
3602
3907
  const notice = formatResumeValidationNotice(resumeValidation, projectRoot);
3603
3908
  if (notice) {
@@ -3635,26 +3940,32 @@ async function executeResumeSession(params) {
3635
3940
  });
3636
3941
  }
3637
3942
  const carriedMessages = data.messages.filter((message) => !isResumeNoticeMessage(message));
3943
+ const recoveredMessages = appendSyntheticRecoveryMessages(
3944
+ carriedMessages,
3945
+ recoveryPlan.pendingEvents,
3946
+ synthesizedResults
3947
+ );
3948
+ const { pendingToolUseCount: _healedPendingToolUseCount, ...dataWithoutPendingToolUseCount } = data;
3949
+ const resumedBase = synthesizedResults.length > 0 ? dataWithoutPendingToolUseCount : data;
3638
3950
  const resumedData = {
3639
- ...data,
3951
+ ...resumedBase,
3640
3952
  ...resumeValidation ? { resumeValidation } : {},
3641
- messages: [...carriedMessages, ...noticeMessages]
3953
+ messages: [...recoveredMessages, ...noticeMessages]
3642
3954
  };
3643
3955
  let handle;
3644
3956
  try {
3645
3957
  handle = await openSessionForAppend(file);
3646
3958
  } catch (err) {
3647
3959
  emitSessionStoreError(events, canonicalId, file, "resume", toErrorMessage(err), false);
3648
- throw new Error(
3649
- `Failed to open session "${canonicalId}" for append: ${toErrorMessage(err)}`,
3650
- { cause: err }
3651
- );
3960
+ throw new Error(`Failed to open session "${canonicalId}" for append: ${toErrorMessage(err)}`, {
3961
+ cause: err
3962
+ });
3652
3963
  }
3653
3964
  try {
3654
3965
  const writer = new FileSessionWriter(
3655
3966
  canonicalId,
3656
3967
  handle,
3657
- (/* @__PURE__ */ new Date()).toISOString(),
3968
+ data.metadata.startedAt,
3658
3969
  {
3659
3970
  id: canonicalId,
3660
3971
  model: data.metadata.model,
@@ -3703,6 +4014,71 @@ async function executeResumeSession(params) {
3703
4014
  throw err;
3704
4015
  }
3705
4016
  }
4017
+ function appendSyntheticRecoveryMessages(messages, pendingEvents, synthesizedResults) {
4018
+ if (synthesizedResults.length === 0) return messages;
4019
+ const next = [...messages];
4020
+ for (const result of synthesizedResults) {
4021
+ if (result.type !== "tool_result") continue;
4022
+ const toolUse = findToolUseBlock(next, result.id) ?? findToolUseBlockInEvents(pendingEvents, result.id);
4023
+ if (!toolUse) continue;
4024
+ if (!isLastMessageToolUseFor(next, result.id)) {
4025
+ next.push({
4026
+ role: "assistant",
4027
+ content: [toolUse],
4028
+ ts: result.ts
4029
+ });
4030
+ }
4031
+ next.push({
4032
+ role: "user",
4033
+ content: [
4034
+ {
4035
+ type: "tool_result",
4036
+ tool_use_id: result.id,
4037
+ content: typeof result.content === "string" ? result.content : JSON.stringify(result.content),
4038
+ is_error: result.isError
4039
+ }
4040
+ ],
4041
+ ts: result.ts
4042
+ });
4043
+ }
4044
+ return next;
4045
+ }
4046
+ function findToolUseBlock(messages, id) {
4047
+ for (const message of messages) {
4048
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
4049
+ for (const block of message.content) {
4050
+ if (block.type === "tool_use" && block.id === id) return block;
4051
+ }
4052
+ }
4053
+ return null;
4054
+ }
4055
+ function findToolUseBlockInEvents(events, id) {
4056
+ for (let i = events.length - 1; i >= 0; i--) {
4057
+ const event = events[i];
4058
+ if (!event) continue;
4059
+ if (event.type === "llm_response") {
4060
+ const block = event.content.find((item) => item.type === "tool_use" && item.id === id);
4061
+ if (block) return block;
4062
+ } else if (event.type === "message_appended" && event.message.role === "assistant" && Array.isArray(event.message.content)) {
4063
+ const block = event.message.content.find(
4064
+ (item) => item.type === "tool_use" && item.id === id
4065
+ );
4066
+ if (block) return block;
4067
+ } else if (event.type === "tool_use" && event.id === id) {
4068
+ return { type: "tool_use", id: event.id, name: event.name, input: recordInput(event.input) };
4069
+ } else if (event.type === "tool_call_start" && event.id === id) {
4070
+ return { type: "tool_use", id: event.id, name: event.name, input: recordInput(event.input) };
4071
+ }
4072
+ }
4073
+ return null;
4074
+ }
4075
+ function recordInput(input) {
4076
+ return input !== null && typeof input === "object" && !Array.isArray(input) ? input : {};
4077
+ }
4078
+ function isLastMessageToolUseFor(messages, id) {
4079
+ const last = messages[messages.length - 1];
4080
+ return last?.role === "assistant" && Array.isArray(last.content) && last.content.some((block) => block.type === "tool_use" && block.id === id);
4081
+ }
3706
4082
  async function openSessionForAppend(file) {
3707
4083
  const handle = await fsp12.open(file, "a+", 384);
3708
4084
  try {
@@ -3723,6 +4099,7 @@ async function openSessionForAppend(file) {
3723
4099
 
3724
4100
  // src/storage/session-store/search-events.ts
3725
4101
  import * as fsp13 from "node:fs/promises";
4102
+ import { StringDecoder } from "node:string_decoder";
3726
4103
  async function searchSessionEvents(args) {
3727
4104
  const { file, secretScrubber, predicate, limit, signal } = args;
3728
4105
  const out = [];
@@ -3739,6 +4116,7 @@ async function searchSessionEvents(args) {
3739
4116
  fh = await fsp13.open(file, "r");
3740
4117
  const CHUNK = 64 * 1024;
3741
4118
  const buf = Buffer.alloc(CHUNK);
4119
+ const decoder = new StringDecoder("utf8");
3742
4120
  let leftover = "";
3743
4121
  let eventIndex = 0;
3744
4122
  for (let position = 0; ; position += buf.byteLength) {
@@ -3748,7 +4126,7 @@ async function searchSessionEvents(args) {
3748
4126
  }
3749
4127
  const { bytesRead } = await fh.read(buf, 0, CHUNK, position);
3750
4128
  if (bytesRead === 0) break;
3751
- const text = leftover + buf.subarray(0, bytesRead).toString("utf8");
4129
+ const text = leftover + decoder.write(buf.subarray(0, bytesRead));
3752
4130
  const parts = text.split("\n");
3753
4131
  leftover = parts.pop() ?? "";
3754
4132
  for (const line of parts) {
@@ -3762,6 +4140,7 @@ async function searchSessionEvents(args) {
3762
4140
  eventIndex++;
3763
4141
  }
3764
4142
  }
4143
+ leftover += decoder.end();
3765
4144
  const trailing = parseSessionEventLine(leftover.trim() ? leftover : "", secretScrubber);
3766
4145
  if (trailing && predicate(trailing, eventIndex, trailing.ts)) {
3767
4146
  out.push({ event: trailing, eventIndex, ts: trailing.ts });
@@ -4037,7 +4416,10 @@ async function compactIndexInner(indexFile, entries, deletedIds) {
4037
4416
  parts.push(JSON.stringify({ action: "delete", id }));
4038
4417
  }
4039
4418
  }
4040
- if (parts.length === 0) return;
4419
+ if (parts.length === 0) {
4420
+ await atomicWrite(indexFile, "", { mode: 384 });
4421
+ return;
4422
+ }
4041
4423
  const lines = parts.join("\n") + "\n";
4042
4424
  await atomicWrite(indexFile, lines, { mode: 384 });
4043
4425
  }
@@ -4412,7 +4794,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
4412
4794
  if (resolution.status === "resolved") return resolution.id;
4413
4795
  throw sessionIdResolutionError(resolution);
4414
4796
  }
4415
- async resume(id) {
4797
+ async resume(id, onLoadProgress) {
4416
4798
  const canonicalId = await this.resolveId(id);
4417
4799
  const file = this.sessionPath(canonicalId, ".jsonl");
4418
4800
  return executeResumeSession({
@@ -4425,7 +4807,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
4425
4807
  checkpointCas: this.checkpointCas,
4426
4808
  onAppend: this.onAppend,
4427
4809
  onAppendBatch: this.onAppendBatch,
4428
- load: (loadId) => this.load(loadId),
4810
+ load: (loadId) => this.load(loadId, onLoadProgress),
4429
4811
  readSummaryManifest: (summaryId) => this.readSummaryManifest(summaryId),
4430
4812
  searchEvents: (searchId, pred) => this.searchEvents(searchId, pred),
4431
4813
  persistCatalogSummary: (sum) => this.persistCatalogSummary(sum),
@@ -4433,13 +4815,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
4433
4815
  sessionsDir: this.dir
4434
4816
  });
4435
4817
  }
4436
- async load(id) {
4437
- return this.loadInternal(id, { full: true });
4818
+ async load(id, onLoadProgress) {
4819
+ return this.loadInternal(id, { full: true }, onLoadProgress);
4438
4820
  }
4439
4821
  async loadEventsOnly(id) {
4440
4822
  return this.loadInternal(id, { full: false });
4441
4823
  }
4442
- async loadInternal(id, mode) {
4824
+ async loadInternal(id, mode, onLoadProgress) {
4443
4825
  const file = this.sessionPath(id, ".jsonl");
4444
4826
  const t0 = Date.now();
4445
4827
  let outcome = "success";
@@ -4451,6 +4833,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
4451
4833
  const cached = this.loadCache.getFresh(id, stat11, mode.full);
4452
4834
  if (cached) {
4453
4835
  cacheHit = true;
4836
+ onLoadProgress?.({ loadedBytes: stat11.size, totalBytes: stat11.size });
4454
4837
  return cached;
4455
4838
  }
4456
4839
  const data = await loadSessionDataFromFile({
@@ -4458,7 +4841,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
4458
4841
  file,
4459
4842
  full: mode.full,
4460
4843
  events: this.events,
4461
- secretScrubber: this.secretScrubber
4844
+ secretScrubber: this.secretScrubber,
4845
+ onLoadProgress
4462
4846
  });
4463
4847
  if (mode.full) {
4464
4848
  this.loadCache.set(id, stat11, data);
@@ -4910,7 +5294,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
4910
5294
  holderId: this.maintenanceHolderId
4911
5295
  });
4912
5296
  }
4913
- const deleted = await pruneSessionFiles(this.dir, maxAgeDays, (id) => this.deleteSession(id));
5297
+ const deleted = await pruneSessionFiles(
5298
+ this.dir,
5299
+ maxAgeDays,
5300
+ (id) => this.deleteSession(id),
5301
+ this.isSessionInUse
5302
+ );
4914
5303
  if (deleted > 0) {
4915
5304
  await this.compactIndex().catch(() => void 0);
4916
5305
  }
@@ -4926,6 +5315,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
4926
5315
  ensureShardDir: (sid) => this.ensureShardDir(sid),
4927
5316
  sessionPath: (sid, ext) => this.sessionPath(sid, ext)
4928
5317
  });
5318
+ if (!this.catalogClient) {
5319
+ await this.appendToIndexStrict(await this.summaryFor(canonical));
5320
+ }
4929
5321
  this.clearLoadCache(canonical);
4930
5322
  if (id !== canonical) this.clearLoadCache(id);
4931
5323
  }
@@ -4954,6 +5346,7 @@ export {
4954
5346
  scrubPersistedSessionSummary,
4955
5347
  extractInterruptedTools,
4956
5348
  SessionRecovery,
5349
+ summarizeSessionFile,
4957
5350
  DefaultSessionStore
4958
5351
  };
4959
- //# sourceMappingURL=chunk-UIWONWVG.js.map
5352
+ //# sourceMappingURL=chunk-NSOFPKGT.js.map