@ai-agent-forge/agent-forge 0.86.7 → 0.87.0

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 (665) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +8 -9
  3. package/dist/artifacts/artifact-store.d.ts.map +1 -1
  4. package/dist/artifacts/artifact-store.js +9 -5
  5. package/dist/artifacts/artifact-store.js.map +1 -1
  6. package/dist/bundle/.build-manifest.json +410 -371
  7. package/dist/bundle/chunks/{chunk-A7GUOX2H.js → chunk-6ZKDNZHA.js} +5 -1
  8. package/dist/bundle/chunks/chunk-BFC2BDVJ.js +13 -0
  9. package/dist/bundle/chunks/chunk-MOHOLL3F.js +1456 -0
  10. package/dist/bundle/chunks/image-resize-worker.js +12 -1
  11. package/dist/bundle/chunks/{virtual-modules-3IZ4MYSQ.js → virtual-modules-KZ5DRH7Q.js} +1 -1
  12. package/dist/bundle/cli.js +1 -1
  13. package/dist/bundle/client.js +1 -1
  14. package/dist/bundle/index.js +2 -2
  15. package/dist/bundle/rpc-entry.js +1 -1
  16. package/dist/cli/args.d.ts.map +1 -1
  17. package/dist/cli/args.js +231 -185
  18. package/dist/cli/args.js.map +1 -1
  19. package/dist/cli/auth-check.d.ts.map +1 -1
  20. package/dist/cli/auth-check.js +3 -2
  21. package/dist/cli/auth-check.js.map +1 -1
  22. package/dist/cli/auth-command.d.ts.map +1 -1
  23. package/dist/cli/auth-command.js +15 -9
  24. package/dist/cli/auth-command.js.map +1 -1
  25. package/dist/cli/credential-print.d.ts.map +1 -1
  26. package/dist/cli/credential-print.js +14 -7
  27. package/dist/cli/credential-print.js.map +1 -1
  28. package/dist/cli/experimental/command.d.ts.map +1 -1
  29. package/dist/cli/experimental/command.js +6 -5
  30. package/dist/cli/experimental/command.js.map +1 -1
  31. package/dist/cli/list-models.d.ts.map +1 -1
  32. package/dist/cli/list-models.js +9 -8
  33. package/dist/cli/list-models.js.map +1 -1
  34. package/dist/cli/plugin-manager-controller.d.ts.map +1 -1
  35. package/dist/cli/plugin-manager-controller.js +2 -1
  36. package/dist/cli/plugin-manager-controller.js.map +1 -1
  37. package/dist/cli/serve.d.ts.map +1 -1
  38. package/dist/cli/serve.js +8 -5
  39. package/dist/cli/serve.js.map +1 -1
  40. package/dist/client/remote-session.d.ts.map +1 -1
  41. package/dist/client/remote-session.js +16 -9
  42. package/dist/client/remote-session.js.map +1 -1
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +2 -1
  45. package/dist/config.js.map +1 -1
  46. package/dist/core/agent-capability-contract.d.ts +0 -7
  47. package/dist/core/agent-capability-contract.d.ts.map +1 -1
  48. package/dist/core/agent-capability-contract.js +72 -68
  49. package/dist/core/agent-capability-contract.js.map +1 -1
  50. package/dist/core/agent-session-bash.d.ts.map +1 -1
  51. package/dist/core/agent-session-bash.js +5 -4
  52. package/dist/core/agent-session-bash.js.map +1 -1
  53. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  54. package/dist/core/agent-session-compaction.js +28 -22
  55. package/dist/core/agent-session-compaction.js.map +1 -1
  56. package/dist/core/agent-session-context.d.ts.map +1 -1
  57. package/dist/core/agent-session-context.js +18 -13
  58. package/dist/core/agent-session-context.js.map +1 -1
  59. package/dist/core/agent-session-model.d.ts.map +1 -1
  60. package/dist/core/agent-session-model.js +2 -1
  61. package/dist/core/agent-session-model.js.map +1 -1
  62. package/dist/core/agent-session-retry.d.ts.map +1 -1
  63. package/dist/core/agent-session-retry.js +3 -2
  64. package/dist/core/agent-session-retry.js.map +1 -1
  65. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  66. package/dist/core/agent-session-runtime.js +7 -6
  67. package/dist/core/agent-session-runtime.js.map +1 -1
  68. package/dist/core/agent-session-services.d.ts +1 -1
  69. package/dist/core/agent-session-services.d.ts.map +1 -1
  70. package/dist/core/agent-session-services.js.map +1 -1
  71. package/dist/core/agent-session-tools.d.ts.map +1 -1
  72. package/dist/core/agent-session-tools.js +3 -2
  73. package/dist/core/agent-session-tools.js.map +1 -1
  74. package/dist/core/agent-session.d.ts +40 -2
  75. package/dist/core/agent-session.d.ts.map +1 -1
  76. package/dist/core/agent-session.js +142 -34
  77. package/dist/core/agent-session.js.map +1 -1
  78. package/dist/core/auth-guidance.d.ts.map +1 -1
  79. package/dist/core/auth-guidance.js +11 -5
  80. package/dist/core/auth-guidance.js.map +1 -1
  81. package/dist/core/auth-storage.d.ts.map +1 -1
  82. package/dist/core/auth-storage.js +9 -6
  83. package/dist/core/auth-storage.js.map +1 -1
  84. package/dist/core/compaction/compaction.d.ts.map +1 -1
  85. package/dist/core/compaction/compaction.js +2 -1
  86. package/dist/core/compaction/compaction.js.map +1 -1
  87. package/dist/core/embedded-sdk.d.ts.map +1 -1
  88. package/dist/core/embedded-sdk.js +10 -6
  89. package/dist/core/embedded-sdk.js.map +1 -1
  90. package/dist/core/export-html/index.d.ts.map +1 -1
  91. package/dist/core/export-html/index.js +9 -4
  92. package/dist/core/export-html/index.js.map +1 -1
  93. package/dist/core/extensions/agent-composition.d.ts.map +1 -1
  94. package/dist/core/extensions/agent-composition.js +26 -19
  95. package/dist/core/extensions/agent-composition.js.map +1 -1
  96. package/dist/core/extensions/bootstrap-agent-loop.d.ts.map +1 -1
  97. package/dist/core/extensions/bootstrap-agent-loop.js +3 -2
  98. package/dist/core/extensions/bootstrap-agent-loop.js.map +1 -1
  99. package/dist/core/extensions/bootstrap-lifecycle.d.ts.map +1 -1
  100. package/dist/core/extensions/bootstrap-lifecycle.js +20 -17
  101. package/dist/core/extensions/bootstrap-lifecycle.js.map +1 -1
  102. package/dist/core/extensions/bootstrap-overflow-policy.d.ts.map +1 -1
  103. package/dist/core/extensions/bootstrap-overflow-policy.js +2 -1
  104. package/dist/core/extensions/bootstrap-overflow-policy.js.map +1 -1
  105. package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +1 -1
  106. package/dist/core/extensions/bootstrap-retry-policy.js +2 -1
  107. package/dist/core/extensions/bootstrap-retry-policy.js.map +1 -1
  108. package/dist/core/extensions/bootstrap-skills.d.ts.map +1 -1
  109. package/dist/core/extensions/bootstrap-skills.js +7 -6
  110. package/dist/core/extensions/bootstrap-skills.js.map +1 -1
  111. package/dist/core/extensions/bootstrap-tools.d.ts.map +1 -1
  112. package/dist/core/extensions/bootstrap-tools.js +2 -1
  113. package/dist/core/extensions/bootstrap-tools.js.map +1 -1
  114. package/dist/core/extensions/capability-plugin.d.ts.map +1 -1
  115. package/dist/core/extensions/capability-plugin.js +37 -13
  116. package/dist/core/extensions/capability-plugin.js.map +1 -1
  117. package/dist/core/extensions/capability-runtime-events.d.ts.map +1 -1
  118. package/dist/core/extensions/capability-runtime-events.js +17 -13
  119. package/dist/core/extensions/capability-runtime-events.js.map +1 -1
  120. package/dist/core/extensions/capability-runtime-guards.d.ts.map +1 -1
  121. package/dist/core/extensions/capability-runtime-guards.js +16 -15
  122. package/dist/core/extensions/capability-runtime-guards.js.map +1 -1
  123. package/dist/core/extensions/capability-runtime-manifest.d.ts.map +1 -1
  124. package/dist/core/extensions/capability-runtime-manifest.js +52 -30
  125. package/dist/core/extensions/capability-runtime-manifest.js.map +1 -1
  126. package/dist/core/extensions/capability-runtime-validation.d.ts.map +1 -1
  127. package/dist/core/extensions/capability-runtime-validation.js +66 -65
  128. package/dist/core/extensions/capability-runtime-validation.js.map +1 -1
  129. package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
  130. package/dist/core/extensions/capability-runtime.js +147 -127
  131. package/dist/core/extensions/capability-runtime.js.map +1 -1
  132. package/dist/core/extensions/compaction-memory-failure-mode.d.ts.map +1 -1
  133. package/dist/core/extensions/compaction-memory-failure-mode.js +3 -2
  134. package/dist/core/extensions/compaction-memory-failure-mode.js.map +1 -1
  135. package/dist/core/extensions/context-strategy-binding.d.ts.map +1 -1
  136. package/dist/core/extensions/context-strategy-binding.js +7 -4
  137. package/dist/core/extensions/context-strategy-binding.js.map +1 -1
  138. package/dist/core/extensions/lifecycle-catalog.d.ts.map +1 -1
  139. package/dist/core/extensions/lifecycle-catalog.js +290 -279
  140. package/dist/core/extensions/lifecycle-catalog.js.map +1 -1
  141. package/dist/core/extensions/lifecycle-delivery-queue.d.ts.map +1 -1
  142. package/dist/core/extensions/lifecycle-delivery-queue.js +3 -2
  143. package/dist/core/extensions/lifecycle-delivery-queue.js.map +1 -1
  144. package/dist/core/facade/coding-agent-facade.d.ts +6 -0
  145. package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
  146. package/dist/core/facade/coding-agent-facade.js +2 -0
  147. package/dist/core/facade/coding-agent-facade.js.map +1 -1
  148. package/dist/core/http-dispatcher.d.ts.map +1 -1
  149. package/dist/core/http-dispatcher.js +2 -1
  150. package/dist/core/http-dispatcher.js.map +1 -1
  151. package/dist/core/i18n/index.d.ts +14 -0
  152. package/dist/core/i18n/index.d.ts.map +1 -0
  153. package/dist/core/i18n/index.js +67 -0
  154. package/dist/core/i18n/index.js.map +1 -0
  155. package/dist/core/i18n/locales/en/example.d.ts +7 -0
  156. package/dist/core/i18n/locales/en/example.d.ts.map +1 -0
  157. package/dist/core/i18n/locales/en/example.js +7 -0
  158. package/dist/core/i18n/locales/en/example.js.map +1 -0
  159. package/dist/core/i18n/locales/zh-cn/cli.d.ts +8 -0
  160. package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -0
  161. package/dist/core/i18n/locales/zh-cn/cli.js +227 -0
  162. package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -0
  163. package/dist/core/i18n/locales/zh-cn/common.d.ts +6 -0
  164. package/dist/core/i18n/locales/zh-cn/common.d.ts.map +1 -0
  165. package/dist/core/i18n/locales/zh-cn/common.js +6 -0
  166. package/dist/core/i18n/locales/zh-cn/common.js.map +1 -0
  167. package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts +13 -0
  168. package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts.map +1 -0
  169. package/dist/core/i18n/locales/zh-cn/core-contract-core.js +173 -0
  170. package/dist/core/i18n/locales/zh-cn/core-contract-core.js.map +1 -0
  171. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts +12 -0
  172. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts.map +1 -0
  173. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js +582 -0
  174. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js.map +1 -0
  175. package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts +9 -0
  176. package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts.map +1 -0
  177. package/dist/core/i18n/locales/zh-cn/core-contract-final.js +55 -0
  178. package/dist/core/i18n/locales/zh-cn/core-contract-final.js.map +1 -0
  179. package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts +11 -0
  180. package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts.map +1 -0
  181. package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js +455 -0
  182. package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js.map +1 -0
  183. package/dist/core/i18n/locales/zh-cn/core-errors.d.ts +9 -0
  184. package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -0
  185. package/dist/core/i18n/locales/zh-cn/core-errors.js +191 -0
  186. package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -0
  187. package/dist/core/i18n/locales/zh-cn/example.d.ts +7 -0
  188. package/dist/core/i18n/locales/zh-cn/example.d.ts.map +1 -0
  189. package/dist/core/i18n/locales/zh-cn/example.js +10 -0
  190. package/dist/core/i18n/locales/zh-cn/example.js.map +1 -0
  191. package/dist/core/i18n/locales/zh-cn/index.d.ts +2 -0
  192. package/dist/core/i18n/locales/zh-cn/index.d.ts.map +1 -0
  193. package/dist/core/i18n/locales/zh-cn/index.js +41 -0
  194. package/dist/core/i18n/locales/zh-cn/index.js.map +1 -0
  195. package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts +10 -0
  196. package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -0
  197. package/dist/core/i18n/locales/zh-cn/interactive-mode.js +265 -0
  198. package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -0
  199. package/dist/core/i18n/locales/zh-cn/keybindings.d.ts +11 -0
  200. package/dist/core/i18n/locales/zh-cn/keybindings.d.ts.map +1 -0
  201. package/dist/core/i18n/locales/zh-cn/keybindings.js +67 -0
  202. package/dist/core/i18n/locales/zh-cn/keybindings.js.map +1 -0
  203. package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts +7 -0
  204. package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts.map +1 -0
  205. package/dist/core/i18n/locales/zh-cn/slash-commands.js +39 -0
  206. package/dist/core/i18n/locales/zh-cn/slash-commands.js.map +1 -0
  207. package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts +10 -0
  208. package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts.map +1 -0
  209. package/dist/core/i18n/locales/zh-cn/tui-builtin.js +15 -0
  210. package/dist/core/i18n/locales/zh-cn/tui-builtin.js.map +1 -0
  211. package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts +13 -0
  212. package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts.map +1 -0
  213. package/dist/core/i18n/locales/zh-cn/tui-components-misc.js +102 -0
  214. package/dist/core/i18n/locales/zh-cn/tui-components-misc.js.map +1 -0
  215. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts +12 -0
  216. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts.map +1 -0
  217. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js +288 -0
  218. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js.map +1 -0
  219. package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts +9 -0
  220. package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts.map +1 -0
  221. package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js +79 -0
  222. package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js.map +1 -0
  223. package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts +8 -0
  224. package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts.map +1 -0
  225. package/dist/core/i18n/locales/zh-cn/tui-self-owned.js +72 -0
  226. package/dist/core/i18n/locales/zh-cn/tui-self-owned.js.map +1 -0
  227. package/dist/core/keybindings.d.ts +15 -0
  228. package/dist/core/keybindings.d.ts.map +1 -1
  229. package/dist/core/keybindings.js +18 -0
  230. package/dist/core/keybindings.js.map +1 -1
  231. package/dist/core/logger.d.ts.map +1 -1
  232. package/dist/core/logger.js +5 -1
  233. package/dist/core/logger.js.map +1 -1
  234. package/dist/core/model-registry.d.ts.map +1 -1
  235. package/dist/core/model-registry.js +2 -1
  236. package/dist/core/model-registry.js.map +1 -1
  237. package/dist/core/model-runtime.d.ts.map +1 -1
  238. package/dist/core/model-runtime.js +8 -4
  239. package/dist/core/model-runtime.js.map +1 -1
  240. package/dist/core/package-install-host.d.ts.map +1 -1
  241. package/dist/core/package-install-host.js +4 -3
  242. package/dist/core/package-install-host.js.map +1 -1
  243. package/dist/core/package-manager.d.ts.map +1 -1
  244. package/dist/core/package-manager.js +15 -10
  245. package/dist/core/package-manager.js.map +1 -1
  246. package/dist/core/package-resource-collection.d.ts.map +1 -1
  247. package/dist/core/package-resource-collection.js +2 -1
  248. package/dist/core/package-resource-collection.js.map +1 -1
  249. package/dist/core/package-source.d.ts.map +1 -1
  250. package/dist/core/package-source.js +3 -2
  251. package/dist/core/package-source.js.map +1 -1
  252. package/dist/core/provider-composer.d.ts.map +1 -1
  253. package/dist/core/provider-composer.js +22 -13
  254. package/dist/core/provider-composer.js.map +1 -1
  255. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  256. package/dist/core/remote-catalog-provider.js +6 -2
  257. package/dist/core/remote-catalog-provider.js.map +1 -1
  258. package/dist/core/resolve-config-value.d.ts.map +1 -1
  259. package/dist/core/resolve-config-value.js +15 -5
  260. package/dist/core/resolve-config-value.js.map +1 -1
  261. package/dist/core/sdk.d.ts +19 -5
  262. package/dist/core/sdk.d.ts.map +1 -1
  263. package/dist/core/sdk.js +74 -54
  264. package/dist/core/sdk.js.map +1 -1
  265. package/dist/core/session-cwd.d.ts.map +1 -1
  266. package/dist/core/session-cwd.js +5 -2
  267. package/dist/core/session-cwd.js.map +1 -1
  268. package/dist/core/session-export.d.ts.map +1 -1
  269. package/dist/core/session-export.js +2 -1
  270. package/dist/core/session-export.js.map +1 -1
  271. package/dist/core/session-manager.d.ts.map +1 -1
  272. package/dist/core/session-manager.js +39 -29
  273. package/dist/core/session-manager.js.map +1 -1
  274. package/dist/core/session-storage.d.ts.map +1 -1
  275. package/dist/core/session-storage.js +3 -3
  276. package/dist/core/session-storage.js.map +1 -1
  277. package/dist/core/settings-manager.d.ts +11 -0
  278. package/dist/core/settings-manager.d.ts.map +1 -1
  279. package/dist/core/settings-manager.js +27 -5
  280. package/dist/core/settings-manager.js.map +1 -1
  281. package/dist/core/slash-commands.d.ts.map +1 -1
  282. package/dist/core/slash-commands.js +53 -32
  283. package/dist/core/slash-commands.js.map +1 -1
  284. package/dist/core/system-prompt.d.ts.map +1 -1
  285. package/dist/core/system-prompt.js +25 -2
  286. package/dist/core/system-prompt.js.map +1 -1
  287. package/dist/core/tools/bash.d.ts.map +1 -1
  288. package/dist/core/tools/bash.js +11 -7
  289. package/dist/core/tools/bash.js.map +1 -1
  290. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  291. package/dist/core/tools/edit-diff.js +9 -4
  292. package/dist/core/tools/edit-diff.js.map +1 -1
  293. package/dist/core/tools/edit.d.ts.map +1 -1
  294. package/dist/core/tools/edit.js +4 -3
  295. package/dist/core/tools/edit.js.map +1 -1
  296. package/dist/core/tools/index.d.ts.map +1 -1
  297. package/dist/core/tools/index.js +3 -2
  298. package/dist/core/tools/index.js.map +1 -1
  299. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  300. package/dist/core/tools/output-accumulator.js +2 -1
  301. package/dist/core/tools/output-accumulator.js.map +1 -1
  302. package/dist/core/tools/read.d.ts.map +1 -1
  303. package/dist/core/tools/read.js +6 -2
  304. package/dist/core/tools/read.js.map +1 -1
  305. package/dist/core/tools/write.d.ts.map +1 -1
  306. package/dist/core/tools/write.js +2 -1
  307. package/dist/core/tools/write.js.map +1 -1
  308. package/dist/durable/durable-service.d.ts.map +1 -1
  309. package/dist/durable/durable-service.js +25 -18
  310. package/dist/durable/durable-service.js.map +1 -1
  311. package/dist/host/checkpoint-commit.d.ts.map +1 -1
  312. package/dist/host/checkpoint-commit.js +2 -1
  313. package/dist/host/checkpoint-commit.js.map +1 -1
  314. package/dist/host/manual-compaction.d.ts.map +1 -1
  315. package/dist/host/manual-compaction.js +2 -1
  316. package/dist/host/manual-compaction.js.map +1 -1
  317. package/dist/host/output-projection.d.ts +0 -13
  318. package/dist/host/output-projection.d.ts.map +1 -1
  319. package/dist/host/output-projection.js +16 -2
  320. package/dist/host/output-projection.js.map +1 -1
  321. package/dist/host/summary-retry.d.ts.map +1 -1
  322. package/dist/host/summary-retry.js +3 -2
  323. package/dist/host/summary-retry.js.map +1 -1
  324. package/dist/main.d.ts.map +1 -1
  325. package/dist/main.js +94 -58
  326. package/dist/main.js.map +1 -1
  327. package/dist/modes/interactive/components/agents-view.d.ts.map +1 -1
  328. package/dist/modes/interactive/components/agents-view.js +18 -15
  329. package/dist/modes/interactive/components/agents-view.js.map +1 -1
  330. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  331. package/dist/modes/interactive/components/assistant-message.js +6 -5
  332. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  333. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/bash-execution.js +12 -7
  335. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  336. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  337. package/dist/modes/interactive/components/bordered-loader.js +2 -1
  338. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  339. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  340. package/dist/modes/interactive/components/branch-summary-message.js +6 -5
  341. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  342. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  343. package/dist/modes/interactive/components/compaction-summary-message.js +9 -5
  344. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  345. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  346. package/dist/modes/interactive/components/config-selector.js +18 -15
  347. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  348. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
  349. package/dist/modes/interactive/components/custom-entry.js +2 -1
  350. package/dist/modes/interactive/components/custom-entry.js.map +1 -1
  351. package/dist/modes/interactive/components/dialog-editor.d.ts.map +1 -1
  352. package/dist/modes/interactive/components/dialog-editor.js +5 -4
  353. package/dist/modes/interactive/components/dialog-editor.js.map +1 -1
  354. package/dist/modes/interactive/components/dialog-input.d.ts.map +1 -1
  355. package/dist/modes/interactive/components/dialog-input.js +2 -1
  356. package/dist/modes/interactive/components/dialog-input.js.map +1 -1
  357. package/dist/modes/interactive/components/dialog-selector.d.ts.map +1 -1
  358. package/dist/modes/interactive/components/dialog-selector.js +4 -3
  359. package/dist/modes/interactive/components/dialog-selector.js.map +1 -1
  360. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  361. package/dist/modes/interactive/components/first-time-setup.js +11 -10
  362. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  363. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  364. package/dist/modes/interactive/components/footer.js +7 -4
  365. package/dist/modes/interactive/components/footer.js.map +1 -1
  366. package/dist/modes/interactive/components/index.d.ts +1 -0
  367. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  368. package/dist/modes/interactive/components/index.js +1 -0
  369. package/dist/modes/interactive/components/index.js.map +1 -1
  370. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  371. package/dist/modes/interactive/components/keybinding-hints.js +3 -2
  372. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  373. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  374. package/dist/modes/interactive/components/login-dialog.js +12 -11
  375. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  376. package/dist/modes/interactive/components/mermaid.d.ts.map +1 -1
  377. package/dist/modes/interactive/components/mermaid.js +6 -2
  378. package/dist/modes/interactive/components/mermaid.js.map +1 -1
  379. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  380. package/dist/modes/interactive/components/model-selector.js +28 -16
  381. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  382. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  383. package/dist/modes/interactive/components/oauth-selector.js +10 -9
  384. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  385. package/dist/modes/interactive/components/plugin-manager-entry.d.ts.map +1 -1
  386. package/dist/modes/interactive/components/plugin-manager-entry.js +22 -13
  387. package/dist/modes/interactive/components/plugin-manager-entry.js.map +1 -1
  388. package/dist/modes/interactive/components/plugin-selection-entry.d.ts.map +1 -1
  389. package/dist/modes/interactive/components/plugin-selection-entry.js +27 -12
  390. package/dist/modes/interactive/components/plugin-selection-entry.js.map +1 -1
  391. package/dist/modes/interactive/components/queue-manager-selector.d.ts +60 -0
  392. package/dist/modes/interactive/components/queue-manager-selector.d.ts.map +1 -0
  393. package/dist/modes/interactive/components/queue-manager-selector.js +166 -0
  394. package/dist/modes/interactive/components/queue-manager-selector.js.map +1 -0
  395. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  396. package/dist/modes/interactive/components/scoped-models-selector.js +16 -14
  397. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  398. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  399. package/dist/modes/interactive/components/session-selector.js +43 -31
  400. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  401. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  402. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  403. package/dist/modes/interactive/components/settings-selector.js +113 -96
  404. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  405. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
  406. package/dist/modes/interactive/components/settings-submenu.js +5 -4
  407. package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
  408. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  409. package/dist/modes/interactive/components/show-images-selector.js +3 -2
  410. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  411. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  412. package/dist/modes/interactive/components/skill-invocation-message.js +4 -3
  413. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  414. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  415. package/dist/modes/interactive/components/status-indicator.js +14 -5
  416. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  417. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  418. package/dist/modes/interactive/components/theme-selector.js +2 -1
  419. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  420. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  421. package/dist/modes/interactive/components/thinking-selector.js +11 -4
  422. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  423. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  424. package/dist/modes/interactive/components/tool-execution.js +8 -3
  425. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  426. package/dist/modes/interactive/components/tree-list.d.ts.map +1 -1
  427. package/dist/modes/interactive/components/tree-list.js +7 -6
  428. package/dist/modes/interactive/components/tree-list.js.map +1 -1
  429. package/dist/modes/interactive/components/tree-render.d.ts.map +1 -1
  430. package/dist/modes/interactive/components/tree-render.js +28 -23
  431. package/dist/modes/interactive/components/tree-render.js.map +1 -1
  432. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  433. package/dist/modes/interactive/components/tree-selector.js +9 -7
  434. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  435. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  436. package/dist/modes/interactive/components/user-message-selector.js +5 -4
  437. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  438. package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -1
  439. package/dist/modes/interactive/components/workflow-view.js +36 -21
  440. package/dist/modes/interactive/components/workflow-view.js.map +1 -1
  441. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  442. package/dist/modes/interactive/external-editor.js +2 -1
  443. package/dist/modes/interactive/external-editor.js.map +1 -1
  444. package/dist/modes/interactive/interactive-auth.d.ts.map +1 -1
  445. package/dist/modes/interactive/interactive-auth.js +71 -40
  446. package/dist/modes/interactive/interactive-auth.js.map +1 -1
  447. package/dist/modes/interactive/interactive-commands.d.ts +1 -0
  448. package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
  449. package/dist/modes/interactive/interactive-commands.js +3 -0
  450. package/dist/modes/interactive/interactive-commands.js.map +1 -1
  451. package/dist/modes/interactive/interactive-dialog-ui.d.ts.map +1 -1
  452. package/dist/modes/interactive/interactive-dialog-ui.js +5 -5
  453. package/dist/modes/interactive/interactive-dialog-ui.js.map +1 -1
  454. package/dist/modes/interactive/interactive-format.d.ts.map +1 -1
  455. package/dist/modes/interactive/interactive-format.js +34 -18
  456. package/dist/modes/interactive/interactive-format.js.map +1 -1
  457. package/dist/modes/interactive/interactive-helpers.d.ts.map +1 -1
  458. package/dist/modes/interactive/interactive-helpers.js +7 -2
  459. package/dist/modes/interactive/interactive-helpers.js.map +1 -1
  460. package/dist/modes/interactive/interactive-mode.d.ts +4 -1
  461. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  462. package/dist/modes/interactive/interactive-mode.js +327 -243
  463. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  464. package/dist/modes/interactive/interactive-selectors.d.ts +9 -0
  465. package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
  466. package/dist/modes/interactive/interactive-selectors.js +79 -25
  467. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  468. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  469. package/dist/modes/interactive/theme/theme-controller.js +5 -1
  470. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  471. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  472. package/dist/modes/interactive/theme/theme.js +26 -23
  473. package/dist/modes/interactive/theme/theme.js.map +1 -1
  474. package/dist/modes/json-event.d.ts.map +1 -1
  475. package/dist/modes/json-event.js +3 -2
  476. package/dist/modes/json-event.js.map +1 -1
  477. package/dist/modes/print-mode.d.ts.map +1 -1
  478. package/dist/modes/print-mode.js +3 -2
  479. package/dist/modes/print-mode.js.map +1 -1
  480. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  481. package/dist/modes/rpc/rpc-client.js +3 -2
  482. package/dist/modes/rpc/rpc-client.js.map +1 -1
  483. package/dist/package-manager-cli.d.ts.map +1 -1
  484. package/dist/package-manager-cli.js +28 -13
  485. package/dist/package-manager-cli.js.map +1 -1
  486. package/dist/plugins/ecosystem/catalog.d.ts.map +1 -1
  487. package/dist/plugins/ecosystem/catalog.js +3 -2
  488. package/dist/plugins/ecosystem/catalog.js.map +1 -1
  489. package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
  490. package/dist/plugins/ecosystem/cli.js +2 -1
  491. package/dist/plugins/ecosystem/cli.js.map +1 -1
  492. package/dist/plugins/ecosystem/default-plugin-market.d.ts.map +1 -1
  493. package/dist/plugins/ecosystem/default-plugin-market.js +3 -2
  494. package/dist/plugins/ecosystem/default-plugin-market.js.map +1 -1
  495. package/dist/plugins/ecosystem/distribution.d.ts.map +1 -1
  496. package/dist/plugins/ecosystem/distribution.js +2 -1
  497. package/dist/plugins/ecosystem/distribution.js.map +1 -1
  498. package/dist/plugins/ecosystem/installer.d.ts.map +1 -1
  499. package/dist/plugins/ecosystem/installer.js +3 -2
  500. package/dist/plugins/ecosystem/installer.js.map +1 -1
  501. package/dist/plugins/ecosystem/node-disk-registry-source.d.ts.map +1 -1
  502. package/dist/plugins/ecosystem/node-disk-registry-source.js +20 -17
  503. package/dist/plugins/ecosystem/node-disk-registry-source.js.map +1 -1
  504. package/dist/plugins/ecosystem/node-distribution-host.d.ts.map +1 -1
  505. package/dist/plugins/ecosystem/node-distribution-host.js +35 -34
  506. package/dist/plugins/ecosystem/node-distribution-host.js.map +1 -1
  507. package/dist/plugins/ecosystem/node-npm-registry-source.d.ts.map +1 -1
  508. package/dist/plugins/ecosystem/node-npm-registry-source.js +35 -34
  509. package/dist/plugins/ecosystem/node-npm-registry-source.js.map +1 -1
  510. package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
  511. package/dist/plugins/ecosystem/node-package-install-host.js +9 -8
  512. package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
  513. package/dist/plugins/ecosystem/node-package-install-persistence.d.ts.map +1 -1
  514. package/dist/plugins/ecosystem/node-package-install-persistence.js +4 -3
  515. package/dist/plugins/ecosystem/node-package-install-persistence.js.map +1 -1
  516. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
  517. package/dist/plugins/ecosystem/node-plugin-bootstrap.js +21 -5
  518. package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
  519. package/dist/plugins/ecosystem/node-plugin-trust-root.d.ts.map +1 -1
  520. package/dist/plugins/ecosystem/node-plugin-trust-root.js +10 -9
  521. package/dist/plugins/ecosystem/node-plugin-trust-root.js.map +1 -1
  522. package/dist/plugins/ecosystem/node-registry-source.d.ts.map +1 -1
  523. package/dist/plugins/ecosystem/node-registry-source.js +31 -30
  524. package/dist/plugins/ecosystem/node-registry-source.js.map +1 -1
  525. package/dist/plugins/ecosystem/node-release-package-source.d.ts.map +1 -1
  526. package/dist/plugins/ecosystem/node-release-package-source.js +30 -29
  527. package/dist/plugins/ecosystem/node-release-package-source.js.map +1 -1
  528. package/dist/plugins/ecosystem/node-tar-writer.d.ts.map +1 -1
  529. package/dist/plugins/ecosystem/node-tar-writer.js +11 -10
  530. package/dist/plugins/ecosystem/node-tar-writer.js.map +1 -1
  531. package/dist/plugins/ecosystem/node-url-package-source.d.ts.map +1 -1
  532. package/dist/plugins/ecosystem/node-url-package-source.js +21 -20
  533. package/dist/plugins/ecosystem/node-url-package-source.js.map +1 -1
  534. package/dist/plugins/ecosystem/plugin-acquisition.d.ts.map +1 -1
  535. package/dist/plugins/ecosystem/plugin-acquisition.js +3 -2
  536. package/dist/plugins/ecosystem/plugin-acquisition.js.map +1 -1
  537. package/dist/plugins/ecosystem/registry-cli-adapter.d.ts.map +1 -1
  538. package/dist/plugins/ecosystem/registry-cli-adapter.js +2 -1
  539. package/dist/plugins/ecosystem/registry-cli-adapter.js.map +1 -1
  540. package/dist/plugins/ecosystem/registry.d.ts.map +1 -1
  541. package/dist/plugins/ecosystem/registry.js +35 -32
  542. package/dist/plugins/ecosystem/registry.js.map +1 -1
  543. package/dist/plugins/ecosystem/resolver.d.ts.map +1 -1
  544. package/dist/plugins/ecosystem/resolver.js +3 -2
  545. package/dist/plugins/ecosystem/resolver.js.map +1 -1
  546. package/dist/plugins/ecosystem/selection-store.d.ts.map +1 -1
  547. package/dist/plugins/ecosystem/selection-store.js +11 -10
  548. package/dist/plugins/ecosystem/selection-store.js.map +1 -1
  549. package/dist/plugins/ecosystem/selection.d.ts.map +1 -1
  550. package/dist/plugins/ecosystem/selection.js +8 -7
  551. package/dist/plugins/ecosystem/selection.js.map +1 -1
  552. package/dist/plugins/ecosystem/state.d.ts.map +1 -1
  553. package/dist/plugins/ecosystem/state.js +24 -23
  554. package/dist/plugins/ecosystem/state.js.map +1 -1
  555. package/dist/plugins/ecosystem/updates.d.ts.map +1 -1
  556. package/dist/plugins/ecosystem/updates.js +5 -4
  557. package/dist/plugins/ecosystem/updates.js.map +1 -1
  558. package/dist/plugins/mcp-http.d.ts +0 -17
  559. package/dist/plugins/mcp-http.d.ts.map +1 -1
  560. package/dist/plugins/mcp-http.js +30 -7
  561. package/dist/plugins/mcp-http.js.map +1 -1
  562. package/dist/plugins/plugin-supervisor.d.ts.map +1 -1
  563. package/dist/plugins/plugin-supervisor.js +19 -1
  564. package/dist/plugins/plugin-supervisor.js.map +1 -1
  565. package/dist/profile/first-roles.d.ts.map +1 -1
  566. package/dist/profile/first-roles.js +10 -1
  567. package/dist/profile/first-roles.js.map +1 -1
  568. package/dist/profile/goal-plugin.d.ts.map +1 -1
  569. package/dist/profile/goal-plugin.js +24 -5
  570. package/dist/profile/goal-plugin.js.map +1 -1
  571. package/dist/profile/identity.d.ts.map +1 -1
  572. package/dist/profile/identity.js +7 -6
  573. package/dist/profile/identity.js.map +1 -1
  574. package/dist/profile/lock.d.ts.map +1 -1
  575. package/dist/profile/lock.js +15 -1
  576. package/dist/profile/lock.js.map +1 -1
  577. package/dist/profile/plan-plugin.d.ts.map +1 -1
  578. package/dist/profile/plan-plugin.js +26 -13
  579. package/dist/profile/plan-plugin.js.map +1 -1
  580. package/dist/profile/plugin-profiles.d.ts.map +1 -1
  581. package/dist/profile/plugin-profiles.js +2 -1
  582. package/dist/profile/plugin-profiles.js.map +1 -1
  583. package/dist/profile/policy-manifest.d.ts.map +1 -1
  584. package/dist/profile/policy-manifest.js +5 -4
  585. package/dist/profile/policy-manifest.js.map +1 -1
  586. package/dist/profile/profile-registry.d.ts +0 -12
  587. package/dist/profile/profile-registry.d.ts.map +1 -1
  588. package/dist/profile/profile-registry.js +14 -1
  589. package/dist/profile/profile-registry.js.map +1 -1
  590. package/dist/profile/role-catalog.d.ts +0 -1
  591. package/dist/profile/role-catalog.d.ts.map +1 -1
  592. package/dist/profile/role-catalog.js +17 -15
  593. package/dist/profile/role-catalog.js.map +1 -1
  594. package/dist/profile/role-orchestrator.d.ts.map +1 -1
  595. package/dist/profile/role-orchestrator.js +8 -7
  596. package/dist/profile/role-orchestrator.js.map +1 -1
  597. package/dist/profile/role-runner.d.ts.map +1 -1
  598. package/dist/profile/role-runner.js +3 -2
  599. package/dist/profile/role-runner.js.map +1 -1
  600. package/dist/profile/root-facade.d.ts.map +1 -1
  601. package/dist/profile/root-facade.js +2 -1
  602. package/dist/profile/root-facade.js.map +1 -1
  603. package/dist/profile/skill-discovery-wiring.d.ts.map +1 -1
  604. package/dist/profile/skill-discovery-wiring.js +7 -2
  605. package/dist/profile/skill-discovery-wiring.js.map +1 -1
  606. package/dist/profile/state-store.d.ts.map +1 -1
  607. package/dist/profile/state-store.js +13 -9
  608. package/dist/profile/state-store.js.map +1 -1
  609. package/dist/profile/todo-plugin.d.ts.map +1 -1
  610. package/dist/profile/todo-plugin.js +9 -4
  611. package/dist/profile/todo-plugin.js.map +1 -1
  612. package/dist/profiles/builtin-suites.d.ts +13 -6
  613. package/dist/profiles/builtin-suites.d.ts.map +1 -1
  614. package/dist/profiles/builtin-suites.js +15 -6
  615. package/dist/profiles/builtin-suites.js.map +1 -1
  616. package/dist/profiles/suite-loader.d.ts +25 -1
  617. package/dist/profiles/suite-loader.d.ts.map +1 -1
  618. package/dist/profiles/suite-loader.js +34 -9
  619. package/dist/profiles/suite-loader.js.map +1 -1
  620. package/dist/remote/server-entry.d.ts.map +1 -1
  621. package/dist/remote/server-entry.js +3 -2
  622. package/dist/remote/server-entry.js.map +1 -1
  623. package/dist/remote/session-source.d.ts.map +1 -1
  624. package/dist/remote/session-source.js +8 -4
  625. package/dist/remote/session-source.js.map +1 -1
  626. package/dist/share/session-share.d.ts.map +1 -1
  627. package/dist/share/session-share.js +14 -2
  628. package/dist/share/session-share.js.map +1 -1
  629. package/dist/testing/plugin-testkit.d.ts.map +1 -1
  630. package/dist/testing/plugin-testkit.js +4 -3
  631. package/dist/testing/plugin-testkit.js.map +1 -1
  632. package/dist/testing/runtime-delivery-queue.d.ts.map +1 -1
  633. package/dist/testing/runtime-delivery-queue.js +2 -1
  634. package/dist/testing/runtime-delivery-queue.js.map +1 -1
  635. package/dist/testing/session-plugin-testkit.d.ts.map +1 -1
  636. package/dist/testing/session-plugin-testkit.js +10 -7
  637. package/dist/testing/session-plugin-testkit.js.map +1 -1
  638. package/dist/utils/ansi.d.ts.map +1 -1
  639. package/dist/utils/ansi.js +2 -1
  640. package/dist/utils/ansi.js.map +1 -1
  641. package/dist/utils/clipboard.d.ts.map +1 -1
  642. package/dist/utils/clipboard.js +7 -6
  643. package/dist/utils/clipboard.js.map +1 -1
  644. package/dist/utils/image-resize-worker.d.ts.map +1 -1
  645. package/dist/utils/image-resize-worker.js +3 -2
  646. package/dist/utils/image-resize-worker.js.map +1 -1
  647. package/dist/utils/shell.d.ts.map +1 -1
  648. package/dist/utils/shell.js +9 -8
  649. package/dist/utils/shell.js.map +1 -1
  650. package/dist/utils/tools-manager.d.ts.map +1 -1
  651. package/dist/utils/tools-manager.js +23 -13
  652. package/dist/utils/tools-manager.js.map +1 -1
  653. package/dist/utils/version-check.d.ts.map +1 -1
  654. package/dist/utils/version-check.js +5 -1
  655. package/dist/utils/version-check.js.map +1 -1
  656. package/docs/keybindings.md +3 -0
  657. package/docs/quickstart.md +1 -1
  658. package/docs/settings.md +1 -0
  659. package/docs/terminal-setup.md +2 -3
  660. package/docs/usage.md +6 -5
  661. package/examples/extensions/with-deps/package-lock.json +2 -2
  662. package/examples/extensions/with-deps/package.json +1 -1
  663. package/examples/extensions/with-deps/plugin.json +1 -1
  664. package/package.json +1 -1
  665. package/dist/bundle/chunks/chunk-EALJ5DDX.js +0 -1452
@@ -1 +1 @@
1
- {"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(`session guard lock timeout after ${lockTimeoutMs} ms: ${lockPath}`);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session file is owned by another live process (pid ${current.pid}): ${sessionFile}. ` +\n\t\t\t\t\t\t\t\"Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport { t } from \"./i18n/index.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tt(\"session guard lock timeout after {timeoutMs} ms: {lockPath}\", { timeoutMs: lockTimeoutMs, lockPath }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tt(\n\t\t\t\t\t\t\t\"Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t\t\t{ pid: current.pid, sessionFile },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -26,6 +26,7 @@ import { dirname, join } from "path";
26
26
  import { StringDecoder } from "string_decoder";
27
27
  import { normalizePath } from "../utils/paths.js";
28
28
  import { sleepSync } from "../utils/sync-sleep.js";
29
+ import { t } from "./i18n/index.js";
29
30
  const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
30
31
  const SESSION_HEADER_READ_BUFFER_SIZE = 4096;
31
32
  /** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */
@@ -155,7 +156,7 @@ function acquireGuardLock(lockPath, lockTimeoutMs, staleLockMs) {
155
156
  continue;
156
157
  }
157
158
  if (Date.now() >= deadline) {
158
- throw new Error(`session guard lock timeout after ${lockTimeoutMs} ms: ${lockPath}`);
159
+ throw new Error(t("session guard lock timeout after {timeoutMs} ms: {lockPath}", { timeoutMs: lockTimeoutMs, lockPath }));
159
160
  }
160
161
  sleepSync(GUARD_LOCK_POLL_INTERVAL_MS);
161
162
  }
@@ -362,8 +363,7 @@ export class FileSessionStorageProvider {
362
363
  if (current !== undefined) {
363
364
  const foreignLive = current.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);
364
365
  if (foreignLive) {
365
- throw new Error(`Session file is owned by another live process (pid ${current.pid}): ${sessionFile}. ` +
366
- "Close that process first; concurrent writers would silently fork the session history.");
366
+ throw new Error(t("Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.", { pid: current.pid, sessionFile }));
367
367
  }
368
368
  }
369
369
  // Free, dead owner, or same-process writer: take (or refresh) ownership.
@@ -1 +1 @@
1
- {"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(`session guard lock timeout after ${lockTimeoutMs} ms: ${lockPath}`);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session file is owned by another live process (pid ${current.pid}): ${sessionFile}. ` +\n\t\t\t\t\t\t\t\"Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../src/core/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage seam for process sessions (D-067 phase 1).\n *\n * `SessionManager` owns the session tree, index, and context logic; a\n * `SessionStorageProvider` owns only physical record storage for one session\n * location. The default implementation is the current local JSONL file\n * protocol, byte-identical to the pre-seam behavior; alternative providers\n * (in-memory for tests, remote backends later) implement the same contract.\n *\n * Scope line: discovery/enrichment reads used by `SessionManager.list` /\n * `listAll` (async streamed `SessionInfo` construction) stay on the fs path\n * for now; they move behind this seam in a later phase.\n *\n * Cross-process write arbitration (D-067 phase 2 S5): providers may expose an\n * optional {@link SessionWriteGuardV1}. The default file provider implements\n * it as a `<sessionFile>.guard` sibling token ({ writerId, hostId, pid }): a\n * write fails explicitly while another LIVE process on the same host owns the\n * session, a dead owner (closed/crashed process) is taken over silently, and\n * same-process writers are never conflicted (in-process sequencing is the\n * caller's concern, unchanged). Cross-host guards are treated as live\n * foreign owners (conservative: no false takeover over shared filesystems).\n */\n\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\ttruncateSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"fs\";\nimport { hostname } from \"os\";\nimport { dirname, join } from \"path\";\nimport { StringDecoder } from \"string_decoder\";\nimport { normalizePath } from \"../utils/paths.ts\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport { t } from \"./i18n/index.ts\";\nimport type { FileEntry, SessionHeader } from \"./session-manager.ts\";\n\nconst SESSION_READ_BUFFER_SIZE = 1024 * 1024;\nconst SESSION_HEADER_READ_BUFFER_SIZE = 4096;\n/** Bound synchronous header discovery while allowing large cwd and custom metadata fields. */\nconst MAX_SESSION_HEADER_SCAN_BYTES = 1024 * 1024;\n\n/**\n * Thrown by `readHeader` when the header scan exceeds the bounded limit.\n * `SessionManager.open` treats it as a discovery optimization miss and falls\n * back to a full load; discovery callers swallow it.\n */\nexport class SessionHeaderScanLimitError extends Error {\n\tconstructor(filePath: string) {\n\t\tsuper(`Session header exceeds ${MAX_SESSION_HEADER_SCAN_BYTES}-byte scan limit: ${filePath}`);\n\t\tthis.name = \"SessionHeaderScanLimitError\";\n\t}\n}\n\n/**\n * Record-level storage contract for sessions, keyed by location path. All\n * methods are synchronous and reflect the append-only JSONL protocol\n * semantics; implementations for other media map the same operations onto\n * their own storage model.\n */\nexport interface SessionStorageProvider {\n\t/** Ensure the storage location for a session directory exists. */\n\tensureDir(dir: string): void;\n\t/** True when the given session location exists. */\n\texists(path: string): boolean;\n\t/** Current byte size of a session location (rollback baseline). Missing locations throw. */\n\tsize(path: string): number;\n\t/**\n\t * Load all entries (header first). A missing location yields `[]`. When the\n\t * stored content ends with an unterminated final line, implementations\n\t * repair it by terminating it (the file protocol appends a `\\n`).\n\t */\n\tloadEntries(path: string): FileEntry[];\n\t/** Bounded header scan used for discovery and cwd resolution. Missing or header-less locations yield `null`. */\n\treadHeader(path: string): SessionHeader | null;\n\t/** Replace the whole stored content (header included). */\n\trewrite(path: string, entries: readonly FileEntry[]): void;\n\t/** Write initial content; fails when the location already exists (\"wx\" semantics). */\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void;\n\t/** Append one entry after the existing content. */\n\tappend(path: string, entry: FileEntry): void;\n\t/** Roll back to a previous byte size after a failed append. */\n\ttruncate(path: string, size: number): void;\n\t/** Remove a session location (used when a failed append created it). */\n\tremove(path: string): void;\n\t/** Enumerate session locations inside a directory. Missing directories throw. */\n\tlistSessionFiles(dir: string): string[];\n\t/** Modification time of a session location, used for recent-session ordering. */\n\tmodifiedTime(path: string): Date;\n\t/**\n\t * Optional cross-process write guard (D-067 phase 2 S5). When present,\n\t * `SessionManager` calls `ensureWritable` before every record write and a\n\t * conflicting live foreign owner fails the write explicitly; absent, no\n\t * arbitration happens (the pre-S5 behavior).\n\t */\n\treadonly guard?: SessionWriteGuardV1;\n}\n\n/**\n * Cross-process write guard for one session location (D-067 phase 2 S5).\n * Implementations must make \"check and take ownership\" atomic against other\n * processes, and must throw a descriptive error when another live process\n * owns the session instead of letting two writers interleave entries.\n */\nexport interface SessionWriteGuardV1 {\n\t/**\n\t * Verifies the caller may write the session and records (or refreshes) its\n\t * ownership. Throws when another live process owns the session.\n\t */\n\tensureWritable(sessionFile: string, writerId: string): void;\n}\n\nfunction parseSessionEntryLine(line: string): FileEntry | null {\n\tif (!line.trim()) return null;\n\ttry {\n\t\treturn JSON.parse(line) as FileEntry;\n\t} catch {\n\t\t// Skip malformed lines\n\t\treturn null;\n\t}\n}\n\n/** Serialize one entry as a protocol line (JSON body plus terminating newline). */\nfunction serializeEntryLine(entry: FileEntry): string {\n\treturn `${JSON.stringify(entry)}\\n`;\n}\n\nfunction parseSessionHeaderCandidate(line: string): SessionHeader | null | undefined {\n\tif (!line.trim()) return undefined;\n\tconst entry = parseSessionEntryLine(line);\n\tif (!entry) return undefined;\n\tif (entry.type !== \"session\" || typeof (entry as { id?: unknown }).id !== \"string\") return null;\n\treturn entry;\n}\n\n/** Suffix of the session write-guard sibling file (`<sessionFile>.guard`). */\nconst GUARD_FILE_SUFFIX = \".guard\";\n/** Wall-clock wait between guard-lock acquisition attempts (ledger protocol value). */\nconst GUARD_LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing guard-lock holder. */\nconst GUARD_LOCK_TIMEOUT_MS = 5000;\n/** Default age at which a guard-lock counts as abandoned and is stolen. */\nconst GUARD_STALE_LOCK_MS = 60_000;\n\ninterface SessionGuardTokenV1 {\n\treadonly writerId: string;\n\treadonly hostId: string;\n\treadonly pid: number;\n}\n\nfunction defaultPidAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\t// EPERM means the process exists but is owned by another user — alive.\n\t\treturn (error as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\n/** Writes every byte of `data` to the fd, looping over partial writeSync results. */\nfunction writeAllSync(fd: number, data: string): void {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n}\n\n/**\n * Takes the guard's cross-process lock (same protocol as the memory ledger's\n * and the durable store's persist locks — exclusive create, poll, mtime\n * stale-lock stealing, token-guarded release; the three implementations stay\n * behaviorally identical by precedent, deliberately without module coupling).\n */\nfunction acquireGuardLock(lockPath: string, lockTimeoutMs: number, staleLockMs: number): string {\n\tconst deadline = Date.now() + lockTimeoutMs;\n\tfor (;;) {\n\t\tmkdirSync(dirname(lockPath), { recursive: true });\n\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\ttry {\n\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\ttry {\n\t\t\t\twriteFileSync(fd, token);\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t\treturn token;\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t// same concurrent-create sharing violation to EPERM. For EPERM a\n\t\t\t// vanished lock retries, a persistent permission failure propagates.\n\t\t\tif (code === \"EPERM\") {\n\t\t\t\tlet vanished = false;\n\t\t\t\ttry {\n\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t} catch (statError) {\n\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\tvanished = true;\n\t\t\t\t}\n\t\t\t\tif (vanished) {\n\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlet stale = false;\n\t\t\ttry {\n\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t} catch {\n\t\t\t\t// The holder released between the failed open and the stat.\n\t\t\t}\n\t\t\tif (stale) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tt(\"session guard lock timeout after {timeoutMs} ms: {lockPath}\", { timeoutMs: lockTimeoutMs, lockPath }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tsleepSync(GUARD_LOCK_POLL_INTERVAL_MS);\n\t\t}\n\t}\n}\n\n/** Releases the guard lock, but only if our own token still owns it. */\nfunction releaseGuardLock(lockPath: string, token: string): void {\n\ttry {\n\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t} catch {\n\t\t// Already gone (or unreadable) — nothing to release.\n\t}\n}\n\n/**\n * The default provider: the local JSONL file protocol, moved verbatim from\n * `SessionManager`. Stateless beyond the injectable guard identity options,\n * so a shared module-level instance is the default for every session manager\n * that does not inject its own provider.\n */\nexport class FileSessionStorageProvider implements SessionStorageProvider {\n\t/** Shared stateless instance used as the default provider. */\n\tstatic readonly instance: FileSessionStorageProvider = new FileSessionStorageProvider();\n\n\tprivate readonly hostId: string;\n\tprivate readonly ownPid: number;\n\tprivate readonly pidAlive: (pid: number) => boolean;\n\n\tconstructor(\n\t\toptions: {\n\t\t\t/** Host identity written into guard tokens; defaults to `os.hostname()`. */\n\t\t\treadonly hostId?: string;\n\t\t\t/** Process identity written into guard tokens; defaults to `process.pid`. */\n\t\t\treadonly ownPid?: number;\n\t\t\t/** Injectable liveness check for deterministic tests; defaults to `process.kill(pid, 0)`. */\n\t\t\treadonly pidAlive?: (pid: number) => boolean;\n\t\t} = {},\n\t) {\n\t\tthis.hostId = options.hostId ?? hostname();\n\t\tthis.ownPid = options.ownPid ?? process.pid;\n\t\tthis.pidAlive = options.pidAlive ?? defaultPidAlive;\n\t}\n\n\tensureDir(dir: string): void {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tsize(path: string): number {\n\t\treturn statSync(path).size;\n\t}\n\n\tloadEntries(path: string): FileEntry[] {\n\t\tconst resolvedFilePath = normalizePath(path);\n\t\tif (!existsSync(resolvedFilePath)) return [];\n\n\t\tconst entries: FileEntry[] = [];\n\t\tlet pending = \"\";\n\t\tconst fd = openSync(resolvedFilePath, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);\n\n\t\t\twhile (true) {\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, buffer.length, null);\n\t\t\t\tif (bytesRead === 0) break;\n\n\t\t\t\tpending += decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tconst entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));\n\t\t\t\t\tif (entry) entries.push(entry);\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = pending.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tpending = pending.slice(lineStart);\n\t\t\t}\n\n\t\t\tpending += decoder.end();\n\t\t\tconst finalEntry = parseSessionEntryLine(pending);\n\t\t\tif (finalEntry) entries.push(finalEntry);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\n\t\t// Validate session header before repairing the file.\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as { id?: unknown }).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (pending) appendFileSync(resolvedFilePath, \"\\n\");\n\t\treturn entries;\n\t}\n\n\treadHeader(path: string): SessionHeader | null {\n\t\tif (!existsSync(path)) return null;\n\t\tconst fd = openSync(path, \"r\");\n\t\ttry {\n\t\t\tconst decoder = new StringDecoder(\"utf8\");\n\t\t\tconst buffer = Buffer.allocUnsafe(SESSION_HEADER_READ_BUFFER_SIZE);\n\t\t\tconst lineChunks: string[] = [];\n\t\t\tlet scannedBytes = 0;\n\n\t\t\twhile (scannedBytes < MAX_SESSION_HEADER_SCAN_BYTES) {\n\t\t\t\tconst readLength = Math.min(buffer.length, MAX_SESSION_HEADER_SCAN_BYTES - scannedBytes);\n\t\t\t\tconst bytesRead = readSync(fd, buffer, 0, readLength, null);\n\t\t\t\tif (bytesRead === 0) {\n\t\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t\t}\n\t\t\t\tscannedBytes += bytesRead;\n\n\t\t\t\tconst chunk = decoder.write(buffer.subarray(0, bytesRead));\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet newlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\twhile (newlineIndex !== -1) {\n\t\t\t\t\tlineChunks.push(chunk.slice(lineStart, newlineIndex));\n\t\t\t\t\tconst header = parseSessionHeaderCandidate(lineChunks.join(\"\"));\n\t\t\t\t\tif (header !== undefined) return header;\n\t\t\t\t\tlineChunks.length = 0;\n\t\t\t\t\tlineStart = newlineIndex + 1;\n\t\t\t\t\tnewlineIndex = chunk.indexOf(\"\\n\", lineStart);\n\t\t\t\t}\n\t\t\t\tlineChunks.push(chunk.slice(lineStart));\n\t\t\t}\n\n\t\t\t// Probe for EOF so a final header without a newline is allowed when it ends\n\t\t\t// exactly at the scan limit. Any additional byte exceeds the bounded scan.\n\t\t\tconst probe = Buffer.allocUnsafe(1);\n\t\t\tif (readSync(fd, probe, 0, probe.length, null) === 0) {\n\t\t\t\tlineChunks.push(decoder.end());\n\t\t\t\treturn parseSessionHeaderCandidate(lineChunks.join(\"\")) ?? null;\n\t\t\t}\n\t\t\tthrow new SessionHeaderScanLimitError(path);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\trewrite(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"w\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tcreateExclusive(path: string, entries: readonly FileEntry[]): void {\n\t\tconst fd = openSync(path, \"wx\");\n\t\ttry {\n\t\t\tfor (const entry of entries) {\n\t\t\t\twriteFileSync(fd, serializeEntryLine(entry));\n\t\t\t}\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t}\n\n\tappend(path: string, entry: FileEntry): void {\n\t\tappendFileSync(path, serializeEntryLine(entry));\n\t}\n\n\ttruncate(path: string, size: number): void {\n\t\ttruncateSync(path, size);\n\t}\n\n\tremove(path: string): void {\n\t\tunlinkSync(path);\n\t}\n\n\tlistSessionFiles(dir: string): string[] {\n\t\tconst resolvedDir = normalizePath(dir);\n\t\treturn readdirSync(resolvedDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(resolvedDir, f));\n\t}\n\n\tmodifiedTime(path: string): Date {\n\t\treturn statSync(path).mtime;\n\t}\n\n\t/**\n\t * Cross-process write guard (D-067 phase 2 S5): `<sessionFile>.guard`\n\t * carries `{ writerId, hostId, pid }`. A write conflicts only while the\n\t * recorded owner is a live process on this host; a dead owner (closed or\n\t * crashed process) and same-process writers never conflict, and cross-host\n\t * guards are treated as live foreign owners (conservative over shared\n\t * filesystems). Check-and-take-ownership is atomic under the guard lock.\n\t */\n\treadonly guard: SessionWriteGuardV1 = {\n\t\tensureWritable: (sessionFile: string, writerId: string): void => {\n\t\t\tconst guardPath = `${sessionFile}${GUARD_FILE_SUFFIX}`;\n\t\t\tconst readToken = (): SessionGuardTokenV1 | undefined => {\n\t\t\t\ttry {\n\t\t\t\t\tconst value: unknown = JSON.parse(readFileSync(guardPath, \"utf8\"));\n\t\t\t\t\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\t\t\t\t\tconst record = value as { writerId?: unknown; hostId?: unknown; pid?: unknown };\n\t\t\t\t\tif (typeof record.writerId !== \"string\" || record.writerId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.hostId !== \"string\" || record.hostId.length === 0) return undefined;\n\t\t\t\t\tif (typeof record.pid !== \"number\" || !Number.isInteger(record.pid)) return undefined;\n\t\t\t\t\treturn { writerId: record.writerId, hostId: record.hostId, pid: record.pid };\n\t\t\t\t} catch {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst current = readToken();\n\t\t\t// Fast path: already ours (most appends), or no owner.\n\t\t\tif (current !== undefined && current.writerId === writerId) return;\n\t\t\tif (current !== undefined) {\n\t\t\t\tconst foreignLive =\n\t\t\t\t\tcurrent.hostId === this.hostId && current.pid !== this.ownPid && this.pidAlive(current.pid);\n\t\t\t\tif (foreignLive) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tt(\n\t\t\t\t\t\t\t\"Session file is owned by another live process (pid {pid}): {sessionFile}. Close that process first; concurrent writers would silently fork the session history.\",\n\t\t\t\t\t\t\t{ pid: current.pid, sessionFile },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Free, dead owner, or same-process writer: take (or refresh) ownership.\n\t\t\tconst token = acquireGuardLock(`${guardPath}.lock`, GUARD_LOCK_TIMEOUT_MS, GUARD_STALE_LOCK_MS);\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(guardPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(\n\t\t\t\t\t\tfd,\n\t\t\t\t\t\tJSON.stringify({ writerId, hostId: this.hostId, pid: this.ownPid } satisfies SessionGuardTokenV1),\n\t\t\t\t\t);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseGuardLock(`${guardPath}.lock`, token);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import type { ThinkingLevel } from "@agent-forge/agent-core";
2
2
  import { type Model, type Transport } from "@agent-forge/ai";
3
3
  import type { TuiMode as RendererTuiMode, ScrollViewScrollbar, TerminalCapabilities, WheelScrollLines } from "@agent-forge/tui";
4
+ import { type I18nLanguage } from "./i18n/index.ts";
4
5
  export interface CompactionModelOverride {
5
6
  reserveTokens?: number;
6
7
  keepRecentTokens?: number;
@@ -31,6 +32,13 @@ export interface RetrySettings {
31
32
  }
32
33
  export type TuiMode = RendererTuiMode;
33
34
  export type FullscreenExitOutput = "transcript" | "resume-hint";
35
+ /**
36
+ * 界面语言(docs/design/国际化与本地化.md):`ui.language` 持久配置面。
37
+ * env `AGENT_FORGE_I18N_LANG` 是更高优先的临时覆盖。
38
+ */
39
+ export interface UiSettings {
40
+ language?: I18nLanguage;
41
+ }
34
42
  export interface TerminalSettings {
35
43
  showImages?: boolean;
36
44
  imageWidthCells?: number;
@@ -131,6 +139,7 @@ export interface Settings {
131
139
  steeringMode?: "all" | "one-at-a-time";
132
140
  followUpMode?: "all" | "one-at-a-time";
133
141
  theme?: string;
142
+ ui?: UiSettings;
134
143
  compaction?: CompactionSettings;
135
144
  branchSummary?: BranchSummarySettings;
136
145
  retry?: RetrySettings;
@@ -404,6 +413,8 @@ export declare class SettingsManager {
404
413
  getDefaultTools(): string[] | undefined;
405
414
  setEnabledModels(patterns: string[] | undefined): void;
406
415
  getDoubleEscapeAction(): "fork" | "tree" | "none";
416
+ getUiLanguage(): I18nLanguage;
417
+ setUiLanguage(language: I18nLanguage): void;
407
418
  setDoubleEscapeAction(action: "fork" | "tree" | "none"): void;
408
419
  getTreeFilterMode(): "default" | "no-tools" | "user-only" | "labeled-only" | "all";
409
420
  setTreeFilterMode(mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all"): void;