@vybestack/llxprt-code-core 0.11.0 → 0.12.0-nightly.260910.2aac6841e

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 (417) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/prompt-config/defaults/default-prompts.json +3 -3
  5. package/dist/src/config/agentClientLifecycle.d.ts +4 -3
  6. package/dist/src/config/agentClientLifecycle.js +27 -14
  7. package/dist/src/config/agentClientLifecycle.js.map +1 -1
  8. package/dist/src/config/config.d.ts +2 -5
  9. package/dist/src/config/config.js +10 -23
  10. package/dist/src/config/config.js.map +1 -1
  11. package/dist/src/config/configBase.d.ts +5 -1
  12. package/dist/src/config/configBase.js +27 -6
  13. package/dist/src/config/configBase.js.map +1 -1
  14. package/dist/src/config/configBaseCore.d.ts +26 -8
  15. package/dist/src/config/configBaseCore.js +30 -26
  16. package/dist/src/config/configBaseCore.js.map +1 -1
  17. package/dist/src/config/configMcpRuntime.d.ts +16 -0
  18. package/dist/src/config/configMcpRuntime.js +31 -0
  19. package/dist/src/config/configMcpRuntime.js.map +1 -0
  20. package/dist/src/config/configMediaDefaults.d.ts +10 -0
  21. package/dist/src/config/configMediaDefaults.js +17 -0
  22. package/dist/src/config/configMediaDefaults.js.map +1 -0
  23. package/dist/src/config/configTestHarness.d.ts +4 -0
  24. package/dist/src/config/configTypes.d.ts +4 -54
  25. package/dist/src/config/configTypes.js +6 -82
  26. package/dist/src/config/configTypes.js.map +1 -1
  27. package/dist/src/config/index.d.ts +1 -2
  28. package/dist/src/config/index.js +1 -2
  29. package/dist/src/config/index.js.map +1 -1
  30. package/dist/src/config/models.d.ts +0 -4
  31. package/dist/src/config/models.js +0 -4
  32. package/dist/src/config/models.js.map +1 -1
  33. package/dist/src/config/toolRegistryFactory.d.ts +2 -2
  34. package/dist/src/core/clientContract.d.ts +4 -6
  35. package/dist/src/core/compression/transcriptPathNotice.d.ts +22 -0
  36. package/dist/src/core/compression/transcriptPathNotice.js +29 -0
  37. package/dist/src/core/compression/transcriptPathNotice.js.map +1 -0
  38. package/dist/src/core/contentGenerator.d.ts +2 -5
  39. package/dist/src/core/contentGenerator.js +10 -24
  40. package/dist/src/core/contentGenerator.js.map +1 -1
  41. package/dist/src/core/logger.d.ts +66 -0
  42. package/dist/src/core/logger.js +345 -30
  43. package/dist/src/core/logger.js.map +1 -1
  44. package/dist/src/core/prompts.d.ts +0 -1
  45. package/dist/src/core/prompts.js +5 -79
  46. package/dist/src/core/prompts.js.map +1 -1
  47. package/dist/src/core/turn.d.ts +3 -3
  48. package/dist/src/hooks/hookTranslator.js +5 -4
  49. package/dist/src/hooks/hookTranslator.js.map +1 -1
  50. package/dist/src/index.d.ts +8 -6
  51. package/dist/src/index.js +8 -6
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/src/llm-types/index.d.ts +1 -1
  54. package/dist/src/llm-types/modelEnvelope.d.ts +2 -1
  55. package/dist/src/llm-types/modelEnvelope.js +17 -1
  56. package/dist/src/llm-types/modelEnvelope.js.map +1 -1
  57. package/dist/src/models/schema.d.ts +40 -40
  58. package/dist/src/policy/policies/read-only.toml +0 -5
  59. package/dist/src/prompt-config/TemplateEngine.js +0 -3
  60. package/dist/src/prompt-config/TemplateEngine.js.map +1 -1
  61. package/dist/src/prompt-config/defaults/providers/gemini/core.md +0 -2
  62. package/dist/src/prompt-config/defaults/providers/gemini/models/gemini-2.5-flash/core.md +0 -2
  63. package/dist/src/prompt-config/defaults/providers/gemini/models/gemini-2.5-flash/gemini-2-5-flash/core.md +0 -2
  64. package/dist/src/prompt-config/prompt-loader.d.ts +1 -1
  65. package/dist/src/prompt-config/prompt-loader.js +4 -4
  66. package/dist/src/prompt-config/prompt-loader.js.map +1 -1
  67. package/dist/src/prompt-config/types.d.ts +0 -1
  68. package/dist/src/recording/CheckpointService.d.ts +4 -0
  69. package/dist/src/recording/CheckpointService.js +67 -46
  70. package/dist/src/recording/CheckpointService.js.map +1 -1
  71. package/dist/src/recording/HistoryMutationService.d.ts +1 -1
  72. package/dist/src/recording/HistoryMutationService.js +27 -4
  73. package/dist/src/recording/HistoryMutationService.js.map +1 -1
  74. package/dist/src/recording/RecordingIntegration.d.ts +36 -2
  75. package/dist/src/recording/RecordingIntegration.js +224 -7
  76. package/dist/src/recording/RecordingIntegration.js.map +1 -1
  77. package/dist/src/recording/ReplayEngine.d.ts +8 -10
  78. package/dist/src/recording/ReplayEngine.js +161 -95
  79. package/dist/src/recording/ReplayEngine.js.map +1 -1
  80. package/dist/src/recording/SessionDiscovery.d.ts +4 -2
  81. package/dist/src/recording/SessionDiscovery.js +13 -5
  82. package/dist/src/recording/SessionDiscovery.js.map +1 -1
  83. package/dist/src/recording/SessionRecordingService.d.ts +23 -3
  84. package/dist/src/recording/SessionRecordingService.js +209 -51
  85. package/dist/src/recording/SessionRecordingService.js.map +1 -1
  86. package/dist/src/recording/SessionTransitionService.d.ts +7 -0
  87. package/dist/src/recording/SessionTransitionService.js +66 -13
  88. package/dist/src/recording/SessionTransitionService.js.map +1 -1
  89. package/dist/src/recording/index.d.ts +1 -0
  90. package/dist/src/recording/index.js +1 -0
  91. package/dist/src/recording/index.js.map +1 -1
  92. package/dist/src/recording/janitor/cleanupTypes.d.ts +3 -0
  93. package/dist/src/recording/janitor/cleanupTypes.js.map +1 -1
  94. package/dist/src/recording/janitor/mediaReclamation.d.ts +23 -0
  95. package/dist/src/recording/janitor/mediaReclamation.js +195 -0
  96. package/dist/src/recording/janitor/mediaReclamation.js.map +1 -0
  97. package/dist/src/recording/janitor/sessionJanitor.js +47 -1
  98. package/dist/src/recording/janitor/sessionJanitor.js.map +1 -1
  99. package/dist/src/recording/janitor/sessionScanner.d.ts +12 -9
  100. package/dist/src/recording/janitor/sessionScanner.js +248 -193
  101. package/dist/src/recording/janitor/sessionScanner.js.map +1 -1
  102. package/dist/src/recording/replay-test-helpers.d.ts +10 -1
  103. package/dist/src/recording/replay-test-helpers.js +24 -2
  104. package/dist/src/recording/replay-test-helpers.js.map +1 -1
  105. package/dist/src/recording/replayCheckpointMetadata.d.ts +28 -0
  106. package/dist/src/recording/replayCheckpointMetadata.js +96 -0
  107. package/dist/src/recording/replayCheckpointMetadata.js.map +1 -0
  108. package/dist/src/recording/replayErrorFormatting.d.ts +6 -0
  109. package/dist/src/recording/replayErrorFormatting.js +38 -0
  110. package/dist/src/recording/replayErrorFormatting.js.map +1 -0
  111. package/dist/src/recording/resumeSession.d.ts +3 -0
  112. package/dist/src/recording/resumeSession.js +7 -1
  113. package/dist/src/recording/resumeSession.js.map +1 -1
  114. package/dist/src/recording/semanticMediaPurgeReplayValidation.d.ts +14 -0
  115. package/dist/src/recording/semanticMediaPurgeReplayValidation.js +24 -0
  116. package/dist/src/recording/semanticMediaPurgeReplayValidation.js.map +1 -0
  117. package/dist/src/recording/session-media-package-blobs.d.ts +8 -0
  118. package/dist/src/recording/session-media-package-blobs.js +72 -0
  119. package/dist/src/recording/session-media-package-blobs.js.map +1 -0
  120. package/dist/src/recording/session-media-package-import.d.ts +26 -0
  121. package/dist/src/recording/session-media-package-import.js +124 -0
  122. package/dist/src/recording/session-media-package-import.js.map +1 -0
  123. package/dist/src/recording/session-media-package-state.d.ts +19 -0
  124. package/dist/src/recording/session-media-package-state.js +56 -0
  125. package/dist/src/recording/session-media-package-state.js.map +1 -0
  126. package/dist/src/recording/session-media-package-validation.d.ts +59 -0
  127. package/dist/src/recording/session-media-package-validation.js +286 -0
  128. package/dist/src/recording/session-media-package-validation.js.map +1 -0
  129. package/dist/src/recording/session-media-package-writer.d.ts +18 -0
  130. package/dist/src/recording/session-media-package-writer.js +70 -0
  131. package/dist/src/recording/session-media-package-writer.js.map +1 -0
  132. package/dist/src/recording/session-media-package.d.ts +27 -0
  133. package/dist/src/recording/session-media-package.js +366 -0
  134. package/dist/src/recording/session-media-package.js.map +1 -0
  135. package/dist/src/recording/types.d.ts +34 -2
  136. package/dist/src/recording/types.js.map +1 -1
  137. package/dist/src/runtime/AgentRuntimeContext.d.ts +22 -5
  138. package/dist/src/runtime/AgentRuntimeLoader.d.ts +6 -0
  139. package/dist/src/runtime/AgentRuntimeLoader.js +25 -6
  140. package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -1
  141. package/dist/src/runtime/contracts/PromptEstimation.d.ts +1 -0
  142. package/dist/src/runtime/contracts/PromptEstimation.js.map +1 -1
  143. package/dist/src/runtime/contracts/RuntimeProvider.d.ts +3 -2
  144. package/dist/src/runtime/contracts/RuntimeProviderManager.d.ts +0 -2
  145. package/dist/src/runtime/createAgentRuntimeContext.js +28 -1
  146. package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -1
  147. package/dist/src/runtime/providerRuntimeContext.d.ts +12 -0
  148. package/dist/src/runtime/providerRuntimeContext.js +3 -0
  149. package/dist/src/runtime/providerRuntimeContext.js.map +1 -1
  150. package/dist/src/runtime/runtimeAdapters.js +2 -2
  151. package/dist/src/runtime/runtimeAdapters.js.map +1 -1
  152. package/dist/src/services/history/ContentConverters.d.ts +11 -7
  153. package/dist/src/services/history/ContentConverters.js +11 -7
  154. package/dist/src/services/history/ContentConverters.js.map +1 -1
  155. package/dist/src/services/history/HistoryService.d.ts +18 -180
  156. package/dist/src/services/history/HistoryService.js +53 -523
  157. package/dist/src/services/history/HistoryService.js.map +1 -1
  158. package/dist/src/services/history/HistoryServiceCore.d.ts +278 -0
  159. package/dist/src/services/history/HistoryServiceCore.js +727 -0
  160. package/dist/src/services/history/HistoryServiceCore.js.map +1 -0
  161. package/dist/src/services/history/IContent.d.ts +118 -16
  162. package/dist/src/services/history/IContent.js +284 -4
  163. package/dist/src/services/history/IContent.js.map +1 -1
  164. package/dist/src/services/history/contentSize.js +22 -2
  165. package/dist/src/services/history/contentSize.js.map +1 -1
  166. package/dist/src/services/history/historyChronology.d.ts +7 -0
  167. package/dist/src/services/history/historyChronology.js +35 -3
  168. package/dist/src/services/history/historyChronology.js.map +1 -1
  169. package/dist/src/services/history/historyCloneUtils.d.ts +5 -4
  170. package/dist/src/services/history/historyCloneUtils.js +23 -16
  171. package/dist/src/services/history/historyCloneUtils.js.map +1 -1
  172. package/dist/src/services/history/historyCompressionQueue.d.ts +33 -0
  173. package/dist/src/services/history/historyCompressionQueue.js +81 -0
  174. package/dist/src/services/history/historyCompressionQueue.js.map +1 -0
  175. package/dist/src/services/history/historyEventTypes.d.ts +3 -0
  176. package/dist/src/services/history/historyMutationFailure.d.ts +15 -0
  177. package/dist/src/services/history/historyMutationFailure.js +22 -0
  178. package/dist/src/services/history/historyMutationFailure.js.map +1 -1
  179. package/dist/src/services/history/historyProviderPipeline.d.ts +1 -1
  180. package/dist/src/services/history/historyProviderPipeline.js +15 -5
  181. package/dist/src/services/history/historyProviderPipeline.js.map +1 -1
  182. package/dist/src/services/history/historyTokenEstimation.js +10 -6
  183. package/dist/src/services/history/historyTokenEstimation.js.map +1 -1
  184. package/dist/src/services/history/historyToolNormalization.d.ts +2 -0
  185. package/dist/src/services/history/historyToolNormalization.js +26 -9
  186. package/dist/src/services/history/historyToolNormalization.js.map +1 -1
  187. package/dist/src/services/history/provider-file-binding.d.ts +11 -0
  188. package/dist/src/services/history/provider-file-binding.js +70 -0
  189. package/dist/src/services/history/provider-file-binding.js.map +1 -0
  190. package/dist/src/services/history/semantic-media-purge.d.ts +66 -0
  191. package/dist/src/services/history/semantic-media-purge.js +404 -0
  192. package/dist/src/services/history/semantic-media-purge.js.map +1 -0
  193. package/dist/src/storage/SessionPersistenceService.d.ts +53 -2
  194. package/dist/src/storage/SessionPersistenceService.js +435 -23
  195. package/dist/src/storage/SessionPersistenceService.js.map +1 -1
  196. package/dist/src/storage/history-media-ownership.d.ts +38 -0
  197. package/dist/src/storage/history-media-ownership.js +136 -0
  198. package/dist/src/storage/history-media-ownership.js.map +1 -0
  199. package/dist/src/storage/local-media-store-child.d.ts +6 -0
  200. package/dist/src/storage/local-media-store-child.js +140 -0
  201. package/dist/src/storage/local-media-store-child.js.map +1 -0
  202. package/dist/src/storage/local-media-store-files.d.ts +44 -0
  203. package/dist/src/storage/local-media-store-files.js +460 -0
  204. package/dist/src/storage/local-media-store-files.js.map +1 -0
  205. package/dist/src/storage/local-media-store-lock-recovery.d.ts +15 -0
  206. package/dist/src/storage/local-media-store-lock-recovery.js +112 -0
  207. package/dist/src/storage/local-media-store-lock-recovery.js.map +1 -0
  208. package/dist/src/storage/local-media-store-persistence.d.ts +48 -0
  209. package/dist/src/storage/local-media-store-persistence.js +443 -0
  210. package/dist/src/storage/local-media-store-persistence.js.map +1 -0
  211. package/dist/src/storage/local-media-store-quota.d.ts +6 -0
  212. package/dist/src/storage/local-media-store-quota.js +66 -0
  213. package/dist/src/storage/local-media-store-quota.js.map +1 -0
  214. package/dist/src/storage/local-media-store-reservations.d.ts +55 -0
  215. package/dist/src/storage/local-media-store-reservations.js +377 -0
  216. package/dist/src/storage/local-media-store-reservations.js.map +1 -0
  217. package/dist/src/storage/local-media-store-types.d.ts +110 -0
  218. package/dist/src/storage/local-media-store-types.js +35 -0
  219. package/dist/src/storage/local-media-store-types.js.map +1 -0
  220. package/dist/src/storage/local-media-store-validation.d.ts +34 -0
  221. package/dist/src/storage/local-media-store-validation.js +449 -0
  222. package/dist/src/storage/local-media-store-validation.js.map +1 -0
  223. package/dist/src/storage/local-media-store.d.ts +47 -0
  224. package/dist/src/storage/local-media-store.js +353 -0
  225. package/dist/src/storage/local-media-store.js.map +1 -0
  226. package/dist/src/storage/media-admission-service.d.ts +49 -0
  227. package/dist/src/storage/media-admission-service.js +333 -0
  228. package/dist/src/storage/media-admission-service.js.map +1 -0
  229. package/dist/src/storage/media-lifecycle-metrics.d.ts +75 -0
  230. package/dist/src/storage/media-lifecycle-metrics.js +105 -0
  231. package/dist/src/storage/media-lifecycle-metrics.js.map +1 -0
  232. package/dist/src/storage/media-reference-lifecycle.d.ts +16 -0
  233. package/dist/src/storage/media-reference-lifecycle.js +84 -0
  234. package/dist/src/storage/media-reference-lifecycle.js.map +1 -0
  235. package/dist/src/storage/request-media-resolver.d.ts +67 -0
  236. package/dist/src/storage/request-media-resolver.js +325 -0
  237. package/dist/src/storage/request-media-resolver.js.map +1 -0
  238. package/dist/src/storage/session-media-service-factories.d.ts +19 -0
  239. package/dist/src/storage/session-media-service-factories.js +34 -0
  240. package/dist/src/storage/session-media-service-factories.js.map +1 -0
  241. package/dist/src/storage/session-persistence-helpers.d.ts +10 -0
  242. package/dist/src/storage/session-persistence-helpers.js +46 -0
  243. package/dist/src/storage/session-persistence-helpers.js.map +1 -0
  244. package/dist/src/test-utils/config.d.ts +2 -0
  245. package/dist/src/test-utils/config.js +5 -5
  246. package/dist/src/test-utils/config.js.map +1 -1
  247. package/dist/src/test-utils/runtime.d.ts +2 -0
  248. package/dist/src/test-utils/runtime.js +1 -2
  249. package/dist/src/test-utils/runtime.js.map +1 -1
  250. package/dist/src/tools-adapters/CoreShellToolHostAdapter.js +1 -9
  251. package/dist/src/tools-adapters/CoreShellToolHostAdapter.js.map +1 -1
  252. package/dist/src/tools-adapters/CoreSkillServiceAdapter.d.ts +0 -2
  253. package/dist/src/tools-adapters/CoreSkillServiceAdapter.js +0 -17
  254. package/dist/src/tools-adapters/CoreSkillServiceAdapter.js.map +1 -1
  255. package/dist/src/tools-adapters/CoreToolHostAdapter.d.ts +0 -21
  256. package/dist/src/tools-adapters/CoreToolHostAdapter.js +0 -8
  257. package/dist/src/tools-adapters/CoreToolHostAdapter.js.map +1 -1
  258. package/dist/src/utils/environmentContext.d.ts +8 -9
  259. package/dist/src/utils/environmentContext.js +11 -22
  260. package/dist/src/utils/environmentContext.js.map +1 -1
  261. package/dist/src/utils/errorParsing.d.ts +1 -2
  262. package/dist/src/utils/errorParsing.js +22 -41
  263. package/dist/src/utils/errorParsing.js.map +1 -1
  264. package/dist/src/utils/generateContentResponseUtilities.js +12 -0
  265. package/dist/src/utils/generateContentResponseUtilities.js.map +1 -1
  266. package/dist/src/utils/summarizer.d.ts +1 -2
  267. package/dist/src/utils/summarizer.js +6 -5
  268. package/dist/src/utils/summarizer.js.map +1 -1
  269. package/dist/src/utils/tool-utils.js +7 -1
  270. package/dist/src/utils/tool-utils.js.map +1 -1
  271. package/index.ts +0 -4
  272. package/package.json +38 -30
  273. package/src/config/agentClientLifecycle.ts +53 -22
  274. package/src/config/config.ts +11 -26
  275. package/src/config/configBase.ts +46 -7
  276. package/src/config/configBaseCore.ts +38 -36
  277. package/src/config/configMcpRuntime.ts +46 -0
  278. package/src/config/configMediaDefaults.ts +19 -0
  279. package/src/config/configTypes.ts +13 -53
  280. package/src/config/index.ts +1 -4
  281. package/src/config/models.ts +0 -6
  282. package/src/config/toolRegistryFactory.ts +2 -2
  283. package/src/core/clientContract.ts +4 -6
  284. package/src/core/compression/transcriptPathNotice.ts +31 -0
  285. package/src/core/contentGenerator.ts +15 -39
  286. package/src/core/logger.ts +370 -39
  287. package/src/core/prompts.ts +4 -114
  288. package/src/core/turn.ts +3 -3
  289. package/src/hooks/hookTranslator.ts +6 -4
  290. package/src/index.ts +7 -7
  291. package/src/llm-types/index.ts +5 -0
  292. package/src/llm-types/modelEnvelope.ts +26 -2
  293. package/src/policy/policies/read-only.toml +0 -5
  294. package/src/prompt-config/TemplateEngine.ts +0 -3
  295. package/src/prompt-config/defaults/providers/gemini/core.md +0 -2
  296. package/src/prompt-config/defaults/providers/gemini/models/gemini-2.5-flash/core.md +0 -2
  297. package/src/prompt-config/defaults/providers/gemini/models/gemini-2.5-flash/gemini-2-5-flash/core.md +0 -2
  298. package/src/prompt-config/prompt-loader.ts +7 -8
  299. package/src/prompt-config/types.ts +0 -1
  300. package/src/recording/CheckpointService.ts +110 -45
  301. package/src/recording/HistoryMutationService.ts +28 -3
  302. package/src/recording/RecordingIntegration.ts +258 -11
  303. package/src/recording/ReplayEngine.ts +220 -110
  304. package/src/recording/SessionDiscovery.ts +30 -4
  305. package/src/recording/SessionRecordingService.ts +253 -55
  306. package/src/recording/SessionTransitionService.ts +75 -12
  307. package/src/recording/index.ts +7 -0
  308. package/src/recording/janitor/cleanupTypes.ts +4 -0
  309. package/src/recording/janitor/mediaReclamation.ts +295 -0
  310. package/src/recording/janitor/sessionJanitor.ts +54 -1
  311. package/src/recording/janitor/sessionScanner.ts +319 -227
  312. package/src/recording/replay-test-helpers.ts +33 -2
  313. package/src/recording/replayCheckpointMetadata.ts +115 -0
  314. package/src/recording/replayErrorFormatting.ts +43 -0
  315. package/src/recording/resumeSession.ts +10 -0
  316. package/src/recording/semanticMediaPurgeReplayValidation.ts +35 -0
  317. package/src/recording/session-media-package-blobs.ts +116 -0
  318. package/src/recording/session-media-package-import.ts +189 -0
  319. package/src/recording/session-media-package-state.ts +120 -0
  320. package/src/recording/session-media-package-validation.ts +400 -0
  321. package/src/recording/session-media-package-writer.ts +117 -0
  322. package/src/recording/session-media-package.ts +692 -0
  323. package/src/recording/types.ts +37 -2
  324. package/src/runtime/AgentRuntimeContext.ts +28 -5
  325. package/src/runtime/AgentRuntimeLoader.ts +32 -7
  326. package/src/runtime/contracts/PromptEstimation.ts +1 -0
  327. package/src/runtime/contracts/RuntimeProvider.ts +3 -8
  328. package/src/runtime/contracts/RuntimeProviderManager.ts +0 -2
  329. package/src/runtime/createAgentRuntimeContext.ts +43 -1
  330. package/src/runtime/providerRuntimeContext.ts +22 -0
  331. package/src/runtime/runtimeAdapters.ts +2 -2
  332. package/src/services/history/ContentConverters.ts +11 -7
  333. package/src/services/history/HistoryService.ts +71 -696
  334. package/src/services/history/HistoryServiceCore.ts +1055 -0
  335. package/src/services/history/IContent.ts +460 -20
  336. package/src/services/history/contentSize.ts +22 -2
  337. package/src/services/history/historyChronology.ts +53 -3
  338. package/src/services/history/historyCloneUtils.ts +26 -22
  339. package/src/services/history/historyCompressionQueue.ts +102 -0
  340. package/src/services/history/historyEventTypes.ts +9 -0
  341. package/src/services/history/historyMutationFailure.ts +33 -0
  342. package/src/services/history/historyProviderPipeline.ts +19 -5
  343. package/src/services/history/historyTokenEstimation.ts +9 -5
  344. package/src/services/history/historyToolNormalization.ts +43 -11
  345. package/src/services/history/provider-file-binding.ts +115 -0
  346. package/src/services/history/semantic-media-purge.ts +593 -0
  347. package/src/storage/SessionPersistenceService.ts +667 -30
  348. package/src/storage/history-media-ownership.ts +185 -0
  349. package/src/storage/local-media-store-child.ts +163 -0
  350. package/src/storage/local-media-store-files.ts +643 -0
  351. package/src/storage/local-media-store-lock-recovery.ts +159 -0
  352. package/src/storage/local-media-store-persistence.ts +737 -0
  353. package/src/storage/local-media-store-quota.ts +111 -0
  354. package/src/storage/local-media-store-reservations.ts +485 -0
  355. package/src/storage/local-media-store-types.ts +161 -0
  356. package/src/storage/local-media-store-validation.ts +643 -0
  357. package/src/storage/local-media-store.ts +696 -0
  358. package/src/storage/media-admission-service.ts +512 -0
  359. package/src/storage/media-lifecycle-metrics.ts +228 -0
  360. package/src/storage/media-reference-lifecycle.ts +164 -0
  361. package/src/storage/request-media-resolver.ts +491 -0
  362. package/src/storage/session-media-service-factories.ts +48 -0
  363. package/src/storage/session-persistence-helpers.ts +85 -0
  364. package/src/test-utils/config.ts +7 -5
  365. package/src/test-utils/runtime.ts +3 -2
  366. package/src/tools-adapters/CoreShellToolHostAdapter.ts +1 -11
  367. package/src/tools-adapters/CoreSkillServiceAdapter.ts +0 -23
  368. package/src/tools-adapters/CoreToolHostAdapter.ts +0 -36
  369. package/src/utils/environmentContext.ts +11 -30
  370. package/src/utils/errorParsing.ts +19 -69
  371. package/src/utils/generateContentResponseUtilities.ts +28 -1
  372. package/src/utils/summarizer.ts +8 -10
  373. package/src/utils/tool-utils.ts +7 -1
  374. package/dist/src/code_assist/codeAssist.d.ts +0 -19
  375. package/dist/src/code_assist/codeAssist.js +0 -41
  376. package/dist/src/code_assist/codeAssist.js.map +0 -1
  377. package/dist/src/code_assist/contentGeneratorAdapters.d.ts +0 -49
  378. package/dist/src/code_assist/contentGeneratorAdapters.js +0 -197
  379. package/dist/src/code_assist/contentGeneratorAdapters.js.map +0 -1
  380. package/dist/src/code_assist/converter.d.ts +0 -74
  381. package/dist/src/code_assist/converter.js +0 -158
  382. package/dist/src/code_assist/converter.js.map +0 -1
  383. package/dist/src/code_assist/googleGenAIWrapper.d.ts +0 -27
  384. package/dist/src/code_assist/googleGenAIWrapper.js +0 -46
  385. package/dist/src/code_assist/googleGenAIWrapper.js.map +0 -1
  386. package/dist/src/code_assist/oauth-credential-storage.d.ts +0 -30
  387. package/dist/src/code_assist/oauth-credential-storage.js +0 -135
  388. package/dist/src/code_assist/oauth-credential-storage.js.map +0 -1
  389. package/dist/src/code_assist/server.d.ts +0 -44
  390. package/dist/src/code_assist/server.js +0 -154
  391. package/dist/src/code_assist/server.js.map +0 -1
  392. package/dist/src/code_assist/setup.d.ts +0 -20
  393. package/dist/src/code_assist/setup.js +0 -126
  394. package/dist/src/code_assist/setup.js.map +0 -1
  395. package/dist/src/code_assist/types.d.ts +0 -161
  396. package/dist/src/code_assist/types.js +0 -46
  397. package/dist/src/code_assist/types.js.map +0 -1
  398. package/dist/src/utils/getFolderStructure.d.ts +0 -31
  399. package/dist/src/utils/getFolderStructure.js +0 -240
  400. package/dist/src/utils/getFolderStructure.js.map +0 -1
  401. package/dist/src/utils/installationManager.d.ts +0 -16
  402. package/dist/src/utils/installationManager.js +0 -51
  403. package/dist/src/utils/installationManager.js.map +0 -1
  404. package/dist/src/utils/llm-edit-fixer.d.ts +0 -25
  405. package/dist/src/utils/llm-edit-fixer.js +0 -120
  406. package/dist/src/utils/llm-edit-fixer.js.map +0 -1
  407. package/src/code_assist/codeAssist.ts +0 -59
  408. package/src/code_assist/contentGeneratorAdapters.ts +0 -275
  409. package/src/code_assist/converter.ts +0 -288
  410. package/src/code_assist/googleGenAIWrapper.ts +0 -90
  411. package/src/code_assist/oauth-credential-storage.ts +0 -171
  412. package/src/code_assist/server.ts +0 -263
  413. package/src/code_assist/setup.ts +0 -189
  414. package/src/code_assist/types.ts +0 -201
  415. package/src/utils/getFolderStructure.ts +0 -382
  416. package/src/utils/installationManager.ts +0 -59
  417. package/src/utils/llm-edit-fixer.ts +0 -157
@@ -10,6 +10,20 @@ import * as crypto from 'node:crypto';
10
10
  import { type IContent } from '../services/history/IContent.js';
11
11
  import type { Storage } from '@vybestack/llxprt-code-settings';
12
12
  import { DebugLogger } from '../debug/index.js';
13
+ import type { LocalMediaStore } from './local-media-store.js';
14
+ import {
15
+ MediaAdmissionService,
16
+ type MediaAdmissionContext,
17
+ } from './media-admission-service.js';
18
+ import {
19
+ collectMediaReferences,
20
+ verifyHistoryMedia,
21
+ } from './media-reference-lifecycle.js';
22
+ import {
23
+ containsMediaDiagnostic,
24
+ errorCode,
25
+ persistenceRequestLowerBound,
26
+ } from './session-persistence-helpers.js';
13
27
  import {
14
28
  type ToolResultDisplay,
15
29
  type ToolCallConfirmationDetails,
@@ -66,6 +80,8 @@ export interface PersistedUIHistoryItem {
66
80
  export interface PersistedSession {
67
81
  /** Schema version for future migrations */
68
82
  version: number;
83
+ /** Monotonic save generation. Absent in legacy persisted sessions. */
84
+ generation?: number;
69
85
  /** Unique session identifier */
70
86
  sessionId: string;
71
87
  /** Hash of project root for validation */
@@ -91,6 +107,53 @@ export interface PersistedSession {
91
107
  */
92
108
  const PERSISTED_SESSION_PREFIX = 'persisted-session-';
93
109
 
110
+ export interface SessionPersistenceServiceOptions {
111
+ readonly mediaStore?: LocalMediaStore;
112
+ readonly maxQueueBytes?: number;
113
+ }
114
+
115
+ export interface PreparedPersistenceSave {
116
+ publish(): Promise<void>;
117
+ rollback(): Promise<void>;
118
+ finalize(): Promise<void>;
119
+ }
120
+
121
+ export interface LoadedSessionMediaOwnership {
122
+ release(): Promise<void>;
123
+ }
124
+
125
+ export interface LoadedPersistedSession extends PersistedSession {
126
+ readonly mediaOwnership: LoadedSessionMediaOwnership;
127
+ }
128
+
129
+ interface PendingPersistenceSave {
130
+ readonly generation: number;
131
+ readonly history: IContent[];
132
+ readonly metadata: PersistedSession['metadata'] | undefined;
133
+ readonly uiHistory: PersistedUIHistoryItem[] | undefined;
134
+ readonly updatedAt: string;
135
+ accountedBytes: number;
136
+ readonly resolve: () => void;
137
+ readonly reject: (error: unknown) => void;
138
+ }
139
+
140
+ interface PreparedSaveState {
141
+ accountedBytes: number;
142
+ admittedHistory: readonly IContent[] | undefined;
143
+ reservedContentIds: readonly string[];
144
+ previousContents: Buffer | undefined;
145
+ ownershipPending: boolean;
146
+ published: boolean;
147
+ settled: boolean;
148
+ readonly admissionContext: MediaAdmissionContext;
149
+ readonly ownerId: string;
150
+ readonly tempPath: string;
151
+ }
152
+
153
+ type PersistenceSaveOutcome =
154
+ | { readonly ok: true }
155
+ | { readonly ok: false; readonly error: unknown };
156
+
94
157
  /**
95
158
  * Service for persisting and restoring conversation sessions.
96
159
  * Enables the --continue flag to resume previous sessions.
@@ -100,10 +163,30 @@ export class SessionPersistenceService {
100
163
  private readonly sessionId: string;
101
164
  private readonly chatsDir: string;
102
165
  private readonly sessionFilePath: string;
166
+ private readonly mediaStore: LocalMediaStore | undefined;
167
+ private readonly maxQueueBytes: number;
168
+ private pendingBytes = 0;
169
+ private nextGeneration = 0;
170
+ private saveInProgress = false;
171
+ private transactionActive = false;
172
+ private readonly transactionWaiters: Array<() => void> = [];
173
+ private readonly saveQueue: PendingPersistenceSave[] = [];
103
174
 
104
- constructor(storage: Storage, sessionId: string) {
175
+ constructor(
176
+ storage: Storage,
177
+ sessionId: string,
178
+ options: SessionPersistenceServiceOptions = {},
179
+ ) {
180
+ const maxQueueBytes = options.maxQueueBytes ?? Number.MAX_SAFE_INTEGER;
181
+ if (!Number.isSafeInteger(maxQueueBytes) || maxQueueBytes < 0) {
182
+ throw new Error(
183
+ 'Session persistence queue byte limit must be a non-negative safe integer',
184
+ );
185
+ }
105
186
  this.storage = storage;
106
187
  this.sessionId = sessionId;
188
+ this.mediaStore = options.mediaStore;
189
+ this.maxQueueBytes = maxQueueBytes;
107
190
  this.chatsDir = path.join(storage.getProjectTempDir(), 'chats');
108
191
 
109
192
  // Use timestamp-based filename for easy "most recent" lookup
@@ -114,6 +197,111 @@ export class SessionPersistenceService {
114
197
  );
115
198
  }
116
199
 
200
+ private preflightQueueRequest(
201
+ history: readonly IContent[],
202
+ metadata: PersistedSession['metadata'] | undefined,
203
+ uiHistory: readonly PersistedUIHistoryItem[] | undefined,
204
+ ): void {
205
+ const lowerBound = persistenceRequestLowerBound(
206
+ history,
207
+ metadata,
208
+ uiHistory,
209
+ );
210
+ if (lowerBound > this.maxQueueBytes - this.pendingBytes) {
211
+ throw new Error(
212
+ `Session persistence queue byte limit exceeded: ${this.pendingBytes} + at least ${lowerBound} > ${this.maxQueueBytes}`,
213
+ );
214
+ }
215
+ }
216
+
217
+ private async releaseAdmission(
218
+ history: readonly IContent[],
219
+ context: MediaAdmissionContext,
220
+ ): Promise<void> {
221
+ if (this.mediaStore === undefined) return;
222
+ await new MediaAdmissionService(this.mediaStore).releaseContents(
223
+ history,
224
+ context,
225
+ );
226
+ }
227
+
228
+ private async releaseSaveOwnership(
229
+ admissionHistory: readonly IContent[] | undefined,
230
+ admissionContext: MediaAdmissionContext,
231
+ reservedContentIds: readonly string[],
232
+ ownerId: string,
233
+ ): Promise<void> {
234
+ const failures: unknown[] = [];
235
+ await this.collectCleanupFailure(failures, () =>
236
+ this.releaseMedia(reservedContentIds, ownerId),
237
+ );
238
+ if (admissionHistory !== undefined) {
239
+ await this.collectCleanupFailure(failures, () =>
240
+ this.releaseAdmission(admissionHistory, admissionContext),
241
+ );
242
+ }
243
+ if (failures.length === 1) throw failures[0];
244
+ if (failures.length > 1) {
245
+ throw new AggregateError(
246
+ failures,
247
+ 'Session media ownership release failed',
248
+ );
249
+ }
250
+ }
251
+
252
+ private async reserveMedia(
253
+ history: readonly IContent[],
254
+ ownerId: string,
255
+ ): Promise<readonly string[]> {
256
+ if (this.mediaStore === undefined) return [];
257
+ const references = collectMediaReferences(history);
258
+ const unique = new Map(
259
+ references.map((reference) => [reference.contentId, reference]),
260
+ );
261
+ const reserved: string[] = [];
262
+ try {
263
+ for (const reference of unique.values()) {
264
+ await this.mediaStore.reserve(reference, ownerId);
265
+ reserved.push(reference.contentId);
266
+ }
267
+ return reserved;
268
+ } catch (error) {
269
+ const releaseFailures: unknown[] = [];
270
+ for (const contentId of reserved) {
271
+ try {
272
+ await this.mediaStore.release(contentId, ownerId);
273
+ } catch (releaseError) {
274
+ releaseFailures.push(releaseError);
275
+ }
276
+ }
277
+ if (releaseFailures.length > 0) {
278
+ throw new AggregateError(
279
+ [error, ...releaseFailures],
280
+ 'Media reservation and rollback failed',
281
+ );
282
+ }
283
+ throw error;
284
+ }
285
+ }
286
+
287
+ private async releaseMedia(
288
+ contentIds: readonly string[],
289
+ ownerId: string,
290
+ ): Promise<void> {
291
+ if (this.mediaStore === undefined) return;
292
+ const failures: unknown[] = [];
293
+ for (const contentId of contentIds) {
294
+ try {
295
+ await this.mediaStore.release(contentId, ownerId);
296
+ } catch (error) {
297
+ failures.push(error);
298
+ }
299
+ }
300
+ if (failures.length > 0) {
301
+ throw new AggregateError(failures, 'Failed to release persisted media');
302
+ }
303
+ }
304
+
117
305
  /**
118
306
  * Get the directory containing persisted sessions
119
307
  */
@@ -136,52 +324,497 @@ export class SessionPersistenceService {
136
324
  metadata?: PersistedSession['metadata'],
137
325
  uiHistory?: PersistedUIHistoryItem[],
138
326
  ): Promise<void> {
327
+ this.preflightQueueRequest(history, metadata, uiHistory);
328
+ const generation = ++this.nextGeneration;
329
+ const historySnapshot = structuredClone(history);
330
+ const metadataSnapshot = structuredClone(metadata);
331
+ const uiHistorySnapshot = structuredClone(uiHistory);
332
+ const updatedAt = new Date().toISOString();
333
+ const queuedSession = this.buildSession(
334
+ generation,
335
+ historySnapshot,
336
+ metadataSnapshot,
337
+ uiHistorySnapshot,
338
+ updatedAt,
339
+ );
340
+ const accountedBytes = Buffer.byteLength(
341
+ JSON.stringify(queuedSession, null, 2),
342
+ 'utf8',
343
+ );
344
+ this.enforcePendingByteIncrease(accountedBytes);
345
+ this.pendingBytes += accountedBytes;
346
+ return new Promise<void>((resolve, reject) => {
347
+ this.saveQueue.push({
348
+ generation,
349
+ history: historySnapshot,
350
+ metadata: metadataSnapshot,
351
+ uiHistory: uiHistorySnapshot,
352
+ updatedAt,
353
+ accountedBytes,
354
+ resolve,
355
+ reject,
356
+ });
357
+ this.startNextSave();
358
+ });
359
+ }
360
+
361
+ private startNextSave(): void {
362
+ if (this.saveInProgress || this.transactionActive) return;
363
+ const pending = this.saveQueue.shift();
364
+ if (pending === undefined) return;
365
+ this.saveInProgress = true;
366
+ void this.executeSave(pending).then(
367
+ () => this.completeSave(pending, { ok: true }),
368
+ (error: unknown) => this.completeSave(pending, { ok: false, error }),
369
+ );
370
+ }
371
+
372
+ private completeSave(
373
+ pending: PendingPersistenceSave,
374
+ outcome: PersistenceSaveOutcome,
375
+ ): void {
376
+ this.pendingBytes -= pending.accountedBytes;
377
+ this.saveInProgress = false;
378
+ if (outcome.ok) pending.resolve();
379
+ else pending.reject(outcome.error);
380
+ this.startNextSave();
381
+ this.notifyIdleWaiters();
382
+ }
383
+
384
+ private async restorePersistenceTarget(
385
+ previousTargetBytes: Buffer | null,
386
+ ): Promise<void> {
387
+ if (previousTargetBytes === null) {
388
+ await fs.promises.rm(this.sessionFilePath, { force: true });
389
+ return;
390
+ }
391
+
392
+ const restorePath = `${this.sessionFilePath}.restore.${crypto.randomUUID()}.tmp`;
139
393
  try {
140
- // Ensure chats directory exists
141
- await fs.promises.mkdir(this.chatsDir, { recursive: true });
394
+ await fs.promises.writeFile(restorePath, previousTargetBytes);
395
+ await fs.promises.rename(restorePath, this.sessionFilePath);
396
+ } catch (error: unknown) {
397
+ try {
398
+ await fs.promises.rm(restorePath, { force: true });
399
+ } catch (cleanupError: unknown) {
400
+ throw new AggregateError(
401
+ [error, cleanupError],
402
+ 'Session persistence restoration and temporary-file cleanup failed',
403
+ );
404
+ }
405
+ throw error;
406
+ }
407
+ }
408
+
409
+ private acquireTransaction(): Promise<void> {
410
+ if (
411
+ !this.saveInProgress &&
412
+ !this.transactionActive &&
413
+ this.saveQueue.length === 0
414
+ ) {
415
+ this.transactionActive = true;
416
+ return Promise.resolve();
417
+ }
418
+ return new Promise<void>((resolve) => {
419
+ this.transactionWaiters.push(resolve);
420
+ });
421
+ }
422
+
423
+ private notifyIdleWaiters(): void {
424
+ if (
425
+ this.saveInProgress ||
426
+ this.transactionActive ||
427
+ this.saveQueue.length > 0
428
+ ) {
429
+ return;
430
+ }
431
+ const waiter = this.transactionWaiters.shift();
432
+ if (waiter !== undefined) {
433
+ this.transactionActive = true;
434
+ waiter();
435
+ }
436
+ }
142
437
 
143
- const session: PersistedSession = {
144
- version: 1,
145
- sessionId: this.sessionId,
146
- projectHash: this.getProjectHash(),
147
- createdAt: this.getCreatedAt(),
148
- updatedAt: new Date().toISOString(),
149
- history,
150
- uiHistory,
151
- metadata,
438
+ async prepareSave(
439
+ history: readonly IContent[],
440
+ ): Promise<PreparedPersistenceSave> {
441
+ this.preflightQueueRequest(history, undefined, undefined);
442
+ await this.acquireTransaction();
443
+ let state: PreparedSaveState | undefined;
444
+ try {
445
+ const generation = ++this.nextGeneration;
446
+ const historySnapshot = structuredClone([...history]);
447
+ const updatedAt = new Date().toISOString();
448
+ const accountedBytes = Buffer.byteLength(
449
+ JSON.stringify(
450
+ this.buildSession(
451
+ generation,
452
+ historySnapshot,
453
+ undefined,
454
+ undefined,
455
+ updatedAt,
456
+ ),
457
+ null,
458
+ 2,
459
+ ),
460
+ 'utf8',
461
+ );
462
+ this.enforcePendingByteIncrease(accountedBytes);
463
+ state = {
464
+ accountedBytes,
465
+ admittedHistory: undefined,
466
+ reservedContentIds: [],
467
+ previousContents: undefined,
468
+ ownershipPending: false,
469
+ published: false,
470
+ settled: false,
471
+ admissionContext: {
472
+ turnId: `persistence-generation-${generation}`,
473
+ source: 'session-persistence-save',
474
+ reservationOwnerScope: `persistence-admission:${this.sessionId}:${generation}:${crypto.randomUUID()}`,
475
+ },
476
+ ownerId: `persistence:${this.sessionId}:${generation}:${crypto.randomUUID()}`,
477
+ tempPath: `${this.sessionFilePath}.${generation}.${crypto.randomUUID()}.tmp`,
152
478
  };
479
+ this.pendingBytes += accountedBytes;
480
+ await this.stagePreparedSave(
481
+ state,
482
+ historySnapshot,
483
+ generation,
484
+ updatedAt,
485
+ );
486
+ return this.preparedSaveLifecycle(state);
487
+ } catch (error: unknown) {
488
+ this.finishPreparedSave(state);
489
+ throw error;
490
+ }
491
+ }
153
492
 
154
- // Write to temp file first, then rename for atomic write
155
- const tempPath = `${this.sessionFilePath}.tmp`;
156
- await fs.promises.writeFile(
157
- tempPath,
158
- JSON.stringify(session, null, 2),
159
- 'utf-8',
493
+ private enforcePendingByteIncrease(bytes: number): void {
494
+ if (bytes > this.maxQueueBytes - this.pendingBytes) {
495
+ throw new Error(
496
+ `Session persistence queue byte limit exceeded: ${this.pendingBytes} + ${bytes} > ${this.maxQueueBytes}`,
160
497
  );
161
- await fs.promises.rename(tempPath, this.sessionFilePath);
498
+ }
499
+ }
500
+
501
+ private async stagePreparedSave(
502
+ state: PreparedSaveState,
503
+ history: IContent[],
504
+ generation: number,
505
+ updatedAt: string,
506
+ ): Promise<void> {
507
+ try {
508
+ state.admittedHistory =
509
+ this.mediaStore === undefined
510
+ ? history
511
+ : await new MediaAdmissionService(this.mediaStore).admitContents(
512
+ history,
513
+ state.admissionContext,
514
+ );
515
+ state.ownershipPending = true;
516
+ await verifyHistoryMedia(
517
+ state.admittedHistory,
518
+ this.mediaStore,
519
+ 'session-persistence-save',
520
+ );
521
+ const serialized = JSON.stringify(
522
+ this.buildSession(
523
+ generation,
524
+ [...state.admittedHistory],
525
+ undefined,
526
+ undefined,
527
+ updatedAt,
528
+ ),
529
+ null,
530
+ 2,
531
+ );
532
+ const serializedBytes = Buffer.byteLength(serialized, 'utf8');
533
+ const accountingDelta = serializedBytes - state.accountedBytes;
534
+ this.enforcePendingByteIncrease(accountingDelta);
535
+ state.accountedBytes = serializedBytes;
536
+ this.pendingBytes += accountingDelta;
537
+ state.reservedContentIds = await this.reserveMedia(
538
+ state.admittedHistory,
539
+ state.ownerId,
540
+ );
541
+ state.previousContents = await this.readPersistenceTarget();
542
+ await fs.promises.mkdir(this.chatsDir, { recursive: true });
543
+ await fs.promises.writeFile(state.tempPath, serialized, 'utf-8');
544
+ } catch (error: unknown) {
545
+ const failures: unknown[] = [error];
546
+ await this.collectCleanupFailure(failures, () =>
547
+ fs.promises.rm(state.tempPath, { force: true }),
548
+ );
549
+ if (state.ownershipPending) {
550
+ const released = await this.collectCleanupFailure(failures, () =>
551
+ this.releasePreparedOwnership(state),
552
+ );
553
+ if (released) state.ownershipPending = false;
554
+ }
555
+ if (failures.length > 1) {
556
+ throw new AggregateError(
557
+ failures,
558
+ 'Session persistence staging failed',
559
+ );
560
+ }
561
+ throw error;
562
+ }
563
+ }
564
+
565
+ private async readPersistenceTarget(): Promise<Buffer | undefined> {
566
+ try {
567
+ return await fs.promises.readFile(this.sessionFilePath);
568
+ } catch (error: unknown) {
569
+ if (errorCode(error) === 'ENOENT') return undefined;
570
+ throw error;
571
+ }
572
+ }
573
+
574
+ private preparedSaveLifecycle(
575
+ state: PreparedSaveState,
576
+ ): PreparedPersistenceSave {
577
+ return {
578
+ publish: () => this.publishPreparedSave(state),
579
+ rollback: () => this.rollbackPreparedSave(state),
580
+ finalize: async () => this.finalizePreparedSave(state),
581
+ };
582
+ }
583
+
584
+ private async publishPreparedSave(state: PreparedSaveState): Promise<void> {
585
+ if (state.settled || state.published) {
586
+ throw new Error('Persistence save was already published or settled');
587
+ }
588
+ await fs.promises.rename(state.tempPath, this.sessionFilePath);
589
+ state.published = true;
590
+ await this.releasePreparedOwnership(state);
591
+ state.ownershipPending = false;
592
+ state.reservedContentIds = [];
593
+ }
594
+
595
+ private async rollbackPreparedSave(state: PreparedSaveState): Promise<void> {
596
+ if (state.settled) return;
597
+ const failures: unknown[] = [];
598
+ await this.collectCleanupFailure(failures, () =>
599
+ state.published
600
+ ? this.restorePersistenceTarget(state.previousContents ?? null)
601
+ : fs.promises.rm(state.tempPath, { force: true }),
602
+ );
603
+ if (state.ownershipPending) {
604
+ const released = await this.collectCleanupFailure(failures, () =>
605
+ this.releasePreparedOwnership(state),
606
+ );
607
+ if (released) {
608
+ state.ownershipPending = false;
609
+ state.reservedContentIds = [];
610
+ }
611
+ }
612
+ this.finishPreparedSave(state);
613
+ if (failures.length === 1) throw failures[0];
614
+ if (failures.length > 1) {
615
+ throw new AggregateError(failures, 'Session persistence rollback failed');
616
+ }
617
+ }
162
618
 
619
+ private async releasePreparedOwnership(
620
+ state: PreparedSaveState,
621
+ ): Promise<void> {
622
+ await this.releaseSaveOwnership(
623
+ state.admittedHistory,
624
+ state.admissionContext,
625
+ state.reservedContentIds,
626
+ state.ownerId,
627
+ );
628
+ }
629
+
630
+ private finalizePreparedSave(state: PreparedSaveState): void {
631
+ if (!state.published) {
632
+ throw new Error('Cannot finalize an unpublished persistence save');
633
+ }
634
+ this.finishPreparedSave(state);
635
+ }
636
+
637
+ private finishPreparedSave(state: PreparedSaveState | undefined): void {
638
+ if (state?.settled === true) return;
639
+ if (state !== undefined) {
640
+ state.settled = true;
641
+ this.pendingBytes -= state.accountedBytes;
642
+ }
643
+ this.transactionActive = false;
644
+ this.startNextSave();
645
+ this.notifyIdleWaiters();
646
+ }
647
+
648
+ private async collectCleanupFailure(
649
+ failures: unknown[],
650
+ cleanup: () => Promise<void>,
651
+ ): Promise<boolean> {
652
+ try {
653
+ await cleanup();
654
+ return true;
655
+ } catch (error: unknown) {
656
+ failures.push(error);
657
+ return false;
658
+ }
659
+ }
660
+
661
+ private async executeSave(pending: PendingPersistenceSave): Promise<void> {
662
+ const admissionContext: MediaAdmissionContext = {
663
+ turnId: `persistence-generation-${pending.generation}`,
664
+ source: 'session-persistence-save',
665
+ reservationOwnerScope: `persistence-admission:${this.sessionId}:${pending.generation}:${crypto.randomUUID()}`,
666
+ };
667
+ const ownerId = `persistence:${this.sessionId}:${pending.generation}:${crypto.randomUUID()}`;
668
+ const tempPath = `${this.sessionFilePath}.${pending.generation}.${crypto.randomUUID()}.tmp`;
669
+ let admittedHistory: IContent[] | undefined;
670
+ let reservedContentIds: readonly string[] = [];
671
+ const failures: unknown[] = [];
672
+ try {
673
+ admittedHistory =
674
+ this.mediaStore === undefined
675
+ ? pending.history
676
+ : await new MediaAdmissionService(this.mediaStore).admitContents(
677
+ pending.history,
678
+ admissionContext,
679
+ );
680
+ await verifyHistoryMedia(
681
+ admittedHistory,
682
+ this.mediaStore,
683
+ 'session-persistence-save',
684
+ );
685
+ const session = this.buildSession(
686
+ pending.generation,
687
+ admittedHistory,
688
+ pending.metadata,
689
+ pending.uiHistory,
690
+ pending.updatedAt,
691
+ );
692
+ const serialized = JSON.stringify(session, null, 2);
693
+ const serializedBytes = Buffer.byteLength(serialized, 'utf8');
694
+ const accountingDelta = serializedBytes - pending.accountedBytes;
695
+ this.enforcePendingByteIncrease(accountingDelta);
696
+ pending.accountedBytes = serializedBytes;
697
+ this.pendingBytes += accountingDelta;
698
+ reservedContentIds = await this.reserveMedia(admittedHistory, ownerId);
699
+ await fs.promises.mkdir(this.chatsDir, { recursive: true });
700
+ await fs.promises.writeFile(tempPath, serialized, 'utf-8');
701
+ await fs.promises.rename(tempPath, this.sessionFilePath);
163
702
  logger.debug('Session saved:', {
164
703
  path: this.sessionFilePath,
165
- historyLength: history.length,
166
- metadata,
704
+ historyLength: admittedHistory.length,
705
+ generation: pending.generation,
706
+ metadata: pending.metadata,
167
707
  });
168
- } catch (error) {
708
+ } catch (error: unknown) {
169
709
  logger.error('Failed to save session:', error);
710
+ failures.push(error);
711
+ try {
712
+ await fs.promises.rm(tempPath, { force: true });
713
+ } catch (cleanupError: unknown) {
714
+ failures.push(cleanupError);
715
+ }
716
+ }
717
+ if (admittedHistory !== undefined) {
718
+ try {
719
+ await this.releaseSaveOwnership(
720
+ admittedHistory,
721
+ admissionContext,
722
+ reservedContentIds,
723
+ ownerId,
724
+ );
725
+ } catch (releaseError: unknown) {
726
+ failures.push(releaseError);
727
+ }
728
+ }
729
+ if (failures.length === 1) throw failures[0];
730
+ if (failures.length > 1) {
731
+ throw new AggregateError(failures, 'Session save failed');
732
+ }
733
+ }
734
+
735
+ private buildSession(
736
+ generation: number,
737
+ history: IContent[],
738
+ metadata: PersistedSession['metadata'] | undefined,
739
+ uiHistory: PersistedUIHistoryItem[] | undefined,
740
+ updatedAt: string,
741
+ ): PersistedSession {
742
+ return {
743
+ version: 1,
744
+ generation,
745
+ sessionId: this.sessionId,
746
+ projectHash: this.getProjectHash(),
747
+ createdAt: this.getCreatedAt(),
748
+ updatedAt,
749
+ history,
750
+ ...(uiHistory === undefined ? {} : { uiHistory }),
751
+ ...(metadata === undefined ? {} : { metadata }),
752
+ };
753
+ }
754
+
755
+ getPendingByteCount(): number {
756
+ return this.pendingBytes;
757
+ }
758
+
759
+ private async loadMediaOwnership(
760
+ session: PersistedSession,
761
+ ): Promise<LoadedPersistedSession> {
762
+ const admissionContext: MediaAdmissionContext = {
763
+ turnId: 'session-persistence-load',
764
+ source: 'session-persistence-load',
765
+ reservationOwnerScope: `persistence-load:${this.sessionId}:${crypto.randomUUID()}`,
766
+ };
767
+ let admittedHistory: IContent[] | undefined;
768
+ try {
769
+ admittedHistory =
770
+ this.mediaStore === undefined
771
+ ? session.history
772
+ : await new MediaAdmissionService(this.mediaStore).admitContents(
773
+ session.history,
774
+ admissionContext,
775
+ );
776
+ await verifyHistoryMedia(
777
+ admittedHistory,
778
+ this.mediaStore,
779
+ 'session-persistence-load',
780
+ );
781
+ } catch (error: unknown) {
782
+ if (admittedHistory === undefined) throw error;
783
+ try {
784
+ await this.releaseAdmission(admittedHistory, admissionContext);
785
+ } catch (cleanupError: unknown) {
786
+ throw new AggregateError(
787
+ [error, cleanupError],
788
+ 'Session load and media ownership release failed',
789
+ );
790
+ }
170
791
  throw error;
171
792
  }
793
+ let released = false;
794
+ return {
795
+ ...session,
796
+ history: admittedHistory,
797
+ mediaOwnership: {
798
+ release: async (): Promise<void> => {
799
+ if (released) return;
800
+ await this.releaseAdmission(admittedHistory, admissionContext);
801
+ released = true;
802
+ },
803
+ },
804
+ };
172
805
  }
173
806
 
174
807
  /**
175
808
  * Load the most recent session for this project
176
809
  */
177
- async loadMostRecent(): Promise<PersistedSession | null> {
810
+ async loadMostRecent(): Promise<LoadedPersistedSession | null> {
178
811
  try {
179
812
  // Find all persisted session files (readdir throws ENOENT if dir doesn't exist)
180
813
  let files: string[];
181
814
  try {
182
815
  files = await fs.promises.readdir(this.chatsDir);
183
- } catch (err) {
184
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
816
+ } catch (err: unknown) {
817
+ if (errorCode(err) === 'ENOENT') {
185
818
  logger.debug('No chats directory found');
186
819
  return null;
187
820
  }
@@ -223,16 +856,20 @@ export class SessionPersistenceService {
223
856
  return null;
224
857
  }
225
858
 
859
+ const loadedSession = await this.loadMediaOwnership(session);
226
860
  logger.debug('Session loaded:', {
227
- sessionId: session.sessionId,
228
- historyLength: session.history.length,
229
- createdAt: session.createdAt,
230
- updatedAt: session.updatedAt,
861
+ sessionId: loadedSession.sessionId,
862
+ historyLength: loadedSession.history.length,
863
+ createdAt: loadedSession.createdAt,
864
+ updatedAt: loadedSession.updatedAt,
231
865
  });
232
866
 
233
- return session;
867
+ return loadedSession;
234
868
  } catch (error) {
235
869
  logger.error('Failed to load session:', error);
870
+ if (containsMediaDiagnostic(error)) {
871
+ throw error;
872
+ }
236
873
 
237
874
  // If file is corrupted, back it up and return null
238
875
  if (error instanceof SyntaxError) {