charness 0.0.0-stage → 3.1.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 (1044) hide show
  1. package/.env.example +88 -0
  2. package/DEVELOPMENT.md +110 -0
  3. package/LICENSE +21 -0
  4. package/MIGRATION_1_TO_2.md +24 -0
  5. package/README.md +1196 -2
  6. package/dist/accessControl.d.ts +71 -0
  7. package/dist/accessControl.d.ts.map +1 -0
  8. package/dist/accessControl.js +108 -0
  9. package/dist/accessControl.js.map +1 -0
  10. package/dist/adapters/adapterEventRouting.d.ts +51 -0
  11. package/dist/adapters/adapterEventRouting.d.ts.map +1 -0
  12. package/dist/adapters/adapterEventRouting.js +74 -0
  13. package/dist/adapters/adapterEventRouting.js.map +1 -0
  14. package/dist/adapters/adapterNames.d.ts +19 -0
  15. package/dist/adapters/adapterNames.d.ts.map +1 -0
  16. package/dist/adapters/adapterNames.js +25 -0
  17. package/dist/adapters/adapterNames.js.map +1 -0
  18. package/dist/adapters/claudeCliAdapter.d.ts +1351 -0
  19. package/dist/adapters/claudeCliAdapter.d.ts.map +1 -0
  20. package/dist/adapters/claudeCliAdapter.js +4239 -0
  21. package/dist/adapters/claudeCliAdapter.js.map +1 -0
  22. package/dist/adapters/claudeConfigDir.d.ts +40 -0
  23. package/dist/adapters/claudeConfigDir.d.ts.map +1 -0
  24. package/dist/adapters/claudeConfigDir.js +3 -0
  25. package/dist/adapters/claudeConfigDir.js.map +1 -0
  26. package/dist/adapters/claudeJsonStreamAdapter.d.ts +580 -0
  27. package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -0
  28. package/dist/adapters/claudeJsonStreamAdapter.js +1891 -0
  29. package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -0
  30. package/dist/adapters/claudePlatformFlags.d.ts +35 -0
  31. package/dist/adapters/claudePlatformFlags.d.ts.map +1 -0
  32. package/dist/adapters/claudePlatformFlags.js +46 -0
  33. package/dist/adapters/claudePlatformFlags.js.map +1 -0
  34. package/dist/adapters/createAdapter.d.ts +260 -0
  35. package/dist/adapters/createAdapter.d.ts.map +1 -0
  36. package/dist/adapters/createAdapter.js +512 -0
  37. package/dist/adapters/createAdapter.js.map +1 -0
  38. package/dist/adapters/openCodeAdapter.d.ts +1469 -0
  39. package/dist/adapters/openCodeAdapter.d.ts.map +1 -0
  40. package/dist/adapters/openCodeAdapter.js +4623 -0
  41. package/dist/adapters/openCodeAdapter.js.map +1 -0
  42. package/dist/adapters/sessionLaunchDefaults.d.ts +28 -0
  43. package/dist/adapters/sessionLaunchDefaults.d.ts.map +1 -0
  44. package/dist/adapters/sessionLaunchDefaults.js +15 -0
  45. package/dist/adapters/sessionLaunchDefaults.js.map +1 -0
  46. package/dist/adapters/terminalAdapter.d.ts +68 -0
  47. package/dist/adapters/terminalAdapter.d.ts.map +1 -0
  48. package/dist/adapters/terminalAdapter.js +371 -0
  49. package/dist/adapters/terminalAdapter.js.map +1 -0
  50. package/dist/agentLogin/agentLogin.d.ts +44 -0
  51. package/dist/agentLogin/agentLogin.d.ts.map +1 -0
  52. package/dist/agentLogin/agentLogin.js +530 -0
  53. package/dist/agentLogin/agentLogin.js.map +1 -0
  54. package/dist/agentTrigger.d.ts +28 -0
  55. package/dist/agentTrigger.d.ts.map +1 -0
  56. package/dist/agentTrigger.js +53 -0
  57. package/dist/agentTrigger.js.map +1 -0
  58. package/dist/apiErrorRetry.d.ts +214 -0
  59. package/dist/apiErrorRetry.d.ts.map +1 -0
  60. package/dist/apiErrorRetry.js +382 -0
  61. package/dist/apiErrorRetry.js.map +1 -0
  62. package/dist/apiRetryKick.d.ts +157 -0
  63. package/dist/apiRetryKick.d.ts.map +1 -0
  64. package/dist/apiRetryKick.js +174 -0
  65. package/dist/apiRetryKick.js.map +1 -0
  66. package/dist/bootClassifier.d.ts +46 -0
  67. package/dist/bootClassifier.d.ts.map +1 -0
  68. package/dist/bootClassifier.js +43 -0
  69. package/dist/bootClassifier.js.map +1 -0
  70. package/dist/bot.d.ts +207 -0
  71. package/dist/bot.d.ts.map +1 -0
  72. package/dist/bot.js +11226 -0
  73. package/dist/bot.js.map +1 -0
  74. package/dist/botFileStorage.d.ts +60 -0
  75. package/dist/botFileStorage.d.ts.map +1 -0
  76. package/dist/botFileStorage.js +205 -0
  77. package/dist/botFileStorage.js.map +1 -0
  78. package/dist/cli/applyDnsFix.d.ts +7 -0
  79. package/dist/cli/applyDnsFix.d.ts.map +1 -0
  80. package/dist/cli/applyDnsFix.js +46 -0
  81. package/dist/cli/applyDnsFix.js.map +1 -0
  82. package/dist/cli/bot.d.ts +27 -0
  83. package/dist/cli/bot.d.ts.map +1 -0
  84. package/dist/cli/bot.js +98 -0
  85. package/dist/cli/bot.js.map +1 -0
  86. package/dist/cli/botEntry.d.ts +3 -0
  87. package/dist/cli/botEntry.d.ts.map +1 -0
  88. package/dist/cli/botEntry.js +11 -0
  89. package/dist/cli/botEntry.js.map +1 -0
  90. package/dist/cli/claudeProfiles.d.ts +42 -0
  91. package/dist/cli/claudeProfiles.d.ts.map +1 -0
  92. package/dist/cli/claudeProfiles.js +106 -0
  93. package/dist/cli/claudeProfiles.js.map +1 -0
  94. package/dist/cli/connectorGuards.d.ts +31 -0
  95. package/dist/cli/connectorGuards.d.ts.map +1 -0
  96. package/dist/cli/connectorGuards.js +159 -0
  97. package/dist/cli/connectorGuards.js.map +1 -0
  98. package/dist/cli/envLoader.d.ts +34 -0
  99. package/dist/cli/envLoader.d.ts.map +1 -0
  100. package/dist/cli/envLoader.js +132 -0
  101. package/dist/cli/envLoader.js.map +1 -0
  102. package/dist/cli/hot.d.ts +82 -0
  103. package/dist/cli/hot.d.ts.map +1 -0
  104. package/dist/cli/hot.js +323 -0
  105. package/dist/cli/hot.js.map +1 -0
  106. package/dist/cli.d.ts +3 -0
  107. package/dist/cli.d.ts.map +1 -0
  108. package/dist/cli.js +140 -0
  109. package/dist/cli.js.map +1 -0
  110. package/dist/connectors/jira/adf.d.ts +74 -0
  111. package/dist/connectors/jira/adf.d.ts.map +1 -0
  112. package/dist/connectors/jira/adf.js +412 -0
  113. package/dist/connectors/jira/adf.js.map +1 -0
  114. package/dist/connectors/jira/agentBinaries.d.ts +14 -0
  115. package/dist/connectors/jira/agentBinaries.d.ts.map +1 -0
  116. package/dist/connectors/jira/agentBinaries.js +75 -0
  117. package/dist/connectors/jira/agentBinaries.js.map +1 -0
  118. package/dist/connectors/jira/answerSink.d.ts +59 -0
  119. package/dist/connectors/jira/answerSink.d.ts.map +1 -0
  120. package/dist/connectors/jira/answerSink.js +234 -0
  121. package/dist/connectors/jira/answerSink.js.map +1 -0
  122. package/dist/connectors/jira/attachmentDownload.d.ts +41 -0
  123. package/dist/connectors/jira/attachmentDownload.d.ts.map +1 -0
  124. package/dist/connectors/jira/attachmentDownload.js +92 -0
  125. package/dist/connectors/jira/attachmentDownload.js.map +1 -0
  126. package/dist/connectors/jira/attachmentTool.d.ts +50 -0
  127. package/dist/connectors/jira/attachmentTool.d.ts.map +1 -0
  128. package/dist/connectors/jira/attachmentTool.js +166 -0
  129. package/dist/connectors/jira/attachmentTool.js.map +1 -0
  130. package/dist/connectors/jira/client.d.ts +522 -0
  131. package/dist/connectors/jira/client.d.ts.map +1 -0
  132. package/dist/connectors/jira/client.js +491 -0
  133. package/dist/connectors/jira/client.js.map +1 -0
  134. package/dist/connectors/jira/config.d.ts +104 -0
  135. package/dist/connectors/jira/config.d.ts.map +1 -0
  136. package/dist/connectors/jira/config.js +323 -0
  137. package/dist/connectors/jira/config.js.map +1 -0
  138. package/dist/connectors/jira/configFile.d.ts +10 -0
  139. package/dist/connectors/jira/configFile.d.ts.map +1 -0
  140. package/dist/connectors/jira/configFile.js +50 -0
  141. package/dist/connectors/jira/configFile.js.map +1 -0
  142. package/dist/connectors/jira/connector.d.ts +71 -0
  143. package/dist/connectors/jira/connector.d.ts.map +1 -0
  144. package/dist/connectors/jira/connector.js +312 -0
  145. package/dist/connectors/jira/connector.js.map +1 -0
  146. package/dist/connectors/jira/contextLedger.d.ts +60 -0
  147. package/dist/connectors/jira/contextLedger.d.ts.map +1 -0
  148. package/dist/connectors/jira/contextLedger.js +196 -0
  149. package/dist/connectors/jira/contextLedger.js.map +1 -0
  150. package/dist/connectors/jira/contextPreamble.d.ts +18 -0
  151. package/dist/connectors/jira/contextPreamble.d.ts.map +1 -0
  152. package/dist/connectors/jira/contextPreamble.js +31 -0
  153. package/dist/connectors/jira/contextPreamble.js.map +1 -0
  154. package/dist/connectors/jira/inbound.d.ts +141 -0
  155. package/dist/connectors/jira/inbound.d.ts.map +1 -0
  156. package/dist/connectors/jira/inbound.js +331 -0
  157. package/dist/connectors/jira/inbound.js.map +1 -0
  158. package/dist/connectors/jira/issueBlocks.d.ts +74 -0
  159. package/dist/connectors/jira/issueBlocks.d.ts.map +1 -0
  160. package/dist/connectors/jira/issueBlocks.js +257 -0
  161. package/dist/connectors/jira/issueBlocks.js.map +1 -0
  162. package/dist/connectors/jira/issueContext.d.ts +31 -0
  163. package/dist/connectors/jira/issueContext.d.ts.map +1 -0
  164. package/dist/connectors/jira/issueContext.js +70 -0
  165. package/dist/connectors/jira/issueContext.js.map +1 -0
  166. package/dist/connectors/jira/issueDelta.d.ts +38 -0
  167. package/dist/connectors/jira/issueDelta.d.ts.map +1 -0
  168. package/dist/connectors/jira/issueDelta.js +59 -0
  169. package/dist/connectors/jira/issueDelta.js.map +1 -0
  170. package/dist/connectors/jira/mediaPlaceholders.d.ts +22 -0
  171. package/dist/connectors/jira/mediaPlaceholders.d.ts.map +1 -0
  172. package/dist/connectors/jira/mediaPlaceholders.js +105 -0
  173. package/dist/connectors/jira/mediaPlaceholders.js.map +1 -0
  174. package/dist/connectors/jira/outbound.d.ts +6 -0
  175. package/dist/connectors/jira/outbound.d.ts.map +1 -0
  176. package/dist/connectors/jira/outbound.js +38 -0
  177. package/dist/connectors/jira/outbound.js.map +1 -0
  178. package/dist/connectors/jira/prompt.d.ts +39 -0
  179. package/dist/connectors/jira/prompt.d.ts.map +1 -0
  180. package/dist/connectors/jira/prompt.js +102 -0
  181. package/dist/connectors/jira/prompt.js.map +1 -0
  182. package/dist/connectors/jira/promptSpill.d.ts +50 -0
  183. package/dist/connectors/jira/promptSpill.d.ts.map +1 -0
  184. package/dist/connectors/jira/promptSpill.js +157 -0
  185. package/dist/connectors/jira/promptSpill.js.map +1 -0
  186. package/dist/connectors/jira/promptText.d.ts +7 -0
  187. package/dist/connectors/jira/promptText.d.ts.map +1 -0
  188. package/dist/connectors/jira/promptText.js +26 -0
  189. package/dist/connectors/jira/promptText.js.map +1 -0
  190. package/dist/connectors/jira/sessionKeyCodec.d.ts +18 -0
  191. package/dist/connectors/jira/sessionKeyCodec.d.ts.map +1 -0
  192. package/dist/connectors/jira/sessionKeyCodec.js +106 -0
  193. package/dist/connectors/jira/sessionKeyCodec.js.map +1 -0
  194. package/dist/connectors/jira/trigger.d.ts +54 -0
  195. package/dist/connectors/jira/trigger.d.ts.map +1 -0
  196. package/dist/connectors/jira/trigger.js +101 -0
  197. package/dist/connectors/jira/trigger.js.map +1 -0
  198. package/dist/connectors/jira/triggerLog.d.ts +53 -0
  199. package/dist/connectors/jira/triggerLog.d.ts.map +1 -0
  200. package/dist/connectors/jira/triggerLog.js +78 -0
  201. package/dist/connectors/jira/triggerLog.js.map +1 -0
  202. package/dist/connectors/jira/trustedGroup.d.ts +54 -0
  203. package/dist/connectors/jira/trustedGroup.d.ts.map +1 -0
  204. package/dist/connectors/jira/trustedGroup.js +98 -0
  205. package/dist/connectors/jira/trustedGroup.js.map +1 -0
  206. package/dist/connectors/jira/unconfirmedPosts.d.ts +58 -0
  207. package/dist/connectors/jira/unconfirmedPosts.d.ts.map +1 -0
  208. package/dist/connectors/jira/unconfirmedPosts.js +105 -0
  209. package/dist/connectors/jira/unconfirmedPosts.js.map +1 -0
  210. package/dist/connectors/telegram/answerSink.d.ts +46 -0
  211. package/dist/connectors/telegram/answerSink.d.ts.map +1 -0
  212. package/dist/connectors/telegram/answerSink.js +71 -0
  213. package/dist/connectors/telegram/answerSink.js.map +1 -0
  214. package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +34 -0
  215. package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -0
  216. package/dist/connectors/telegram/commands/autoContinueLimits.js +112 -0
  217. package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -0
  218. package/dist/connectors/telegram/commands/botCore.d.ts +46 -0
  219. package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -0
  220. package/dist/connectors/telegram/commands/botCore.js +3 -0
  221. package/dist/connectors/telegram/commands/botCore.js.map +1 -0
  222. package/dist/connectors/telegram/commands/callbackMessage.d.ts +12 -0
  223. package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -0
  224. package/dist/connectors/telegram/commands/callbackMessage.js +13 -0
  225. package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -0
  226. package/dist/connectors/telegram/commands/claudeAccounts.d.ts +58 -0
  227. package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -0
  228. package/dist/connectors/telegram/commands/claudeAccounts.js +591 -0
  229. package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -0
  230. package/dist/connectors/telegram/commands/compaction.d.ts +47 -0
  231. package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -0
  232. package/dist/connectors/telegram/commands/compaction.js +868 -0
  233. package/dist/connectors/telegram/commands/compaction.js.map +1 -0
  234. package/dist/connectors/telegram/commands/displayModes.d.ts +13 -0
  235. package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -0
  236. package/dist/connectors/telegram/commands/displayModes.js +335 -0
  237. package/dist/connectors/telegram/commands/displayModes.js.map +1 -0
  238. package/dist/connectors/telegram/commands/fallbackChain.d.ts +49 -0
  239. package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -0
  240. package/dist/connectors/telegram/commands/fallbackChain.js +624 -0
  241. package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -0
  242. package/dist/connectors/telegram/commands/modelProviders.d.ts +117 -0
  243. package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -0
  244. package/dist/connectors/telegram/commands/modelProviders.js +966 -0
  245. package/dist/connectors/telegram/commands/modelProviders.js.map +1 -0
  246. package/dist/connectors/telegram/commands/reminders.d.ts +54 -0
  247. package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -0
  248. package/dist/connectors/telegram/commands/reminders.js +624 -0
  249. package/dist/connectors/telegram/commands/reminders.js.map +1 -0
  250. package/dist/connectors/telegram/fileIntake.d.ts +166 -0
  251. package/dist/connectors/telegram/fileIntake.d.ts.map +1 -0
  252. package/dist/connectors/telegram/fileIntake.js +304 -0
  253. package/dist/connectors/telegram/fileIntake.js.map +1 -0
  254. package/dist/connectors/telegram/fileSendGateway.d.ts +57 -0
  255. package/dist/connectors/telegram/fileSendGateway.d.ts.map +1 -0
  256. package/dist/connectors/telegram/fileSendGateway.js +221 -0
  257. package/dist/connectors/telegram/fileSendGateway.js.map +1 -0
  258. package/dist/connectors/telegram/foreignKeyFallbacks.d.ts +52 -0
  259. package/dist/connectors/telegram/foreignKeyFallbacks.d.ts.map +1 -0
  260. package/dist/connectors/telegram/foreignKeyFallbacks.js +71 -0
  261. package/dist/connectors/telegram/foreignKeyFallbacks.js.map +1 -0
  262. package/dist/connectors/telegram/inbound.d.ts +131 -0
  263. package/dist/connectors/telegram/inbound.d.ts.map +1 -0
  264. package/dist/connectors/telegram/inbound.js +278 -0
  265. package/dist/connectors/telegram/inbound.js.map +1 -0
  266. package/dist/connectors/telegram/languagePicker.d.ts +32 -0
  267. package/dist/connectors/telegram/languagePicker.d.ts.map +1 -0
  268. package/dist/connectors/telegram/languagePicker.js +55 -0
  269. package/dist/connectors/telegram/languagePicker.js.map +1 -0
  270. package/dist/connectors/telegram/messageSplit.d.ts +62 -0
  271. package/dist/connectors/telegram/messageSplit.d.ts.map +1 -0
  272. package/dist/connectors/telegram/messageSplit.js +119 -0
  273. package/dist/connectors/telegram/messageSplit.js.map +1 -0
  274. package/dist/connectors/telegram/outbound.d.ts +62 -0
  275. package/dist/connectors/telegram/outbound.d.ts.map +1 -0
  276. package/dist/connectors/telegram/outbound.js +116 -0
  277. package/dist/connectors/telegram/outbound.js.map +1 -0
  278. package/dist/connectors/telegram/output/createOutputTransport.d.ts +34 -0
  279. package/dist/connectors/telegram/output/createOutputTransport.d.ts.map +1 -0
  280. package/dist/connectors/telegram/output/createOutputTransport.js +77 -0
  281. package/dist/connectors/telegram/output/createOutputTransport.js.map +1 -0
  282. package/dist/connectors/telegram/output/dmOutputTransport.d.ts +47 -0
  283. package/dist/connectors/telegram/output/dmOutputTransport.d.ts.map +1 -0
  284. package/dist/connectors/telegram/output/dmOutputTransport.js +412 -0
  285. package/dist/connectors/telegram/output/dmOutputTransport.js.map +1 -0
  286. package/dist/connectors/telegram/outputBacklogGlue.d.ts +38 -0
  287. package/dist/connectors/telegram/outputBacklogGlue.d.ts.map +1 -0
  288. package/dist/connectors/telegram/outputBacklogGlue.js +49 -0
  289. package/dist/connectors/telegram/outputBacklogGlue.js.map +1 -0
  290. package/dist/connectors/telegram/outputFlushPlan.d.ts +93 -0
  291. package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -0
  292. package/dist/connectors/telegram/outputFlushPlan.js +120 -0
  293. package/dist/connectors/telegram/outputFlushPlan.js.map +1 -0
  294. package/dist/connectors/telegram/questionKeyboards.d.ts +30 -0
  295. package/dist/connectors/telegram/questionKeyboards.d.ts.map +1 -0
  296. package/dist/connectors/telegram/questionKeyboards.js +37 -0
  297. package/dist/connectors/telegram/questionKeyboards.js.map +1 -0
  298. package/dist/connectors/telegram/renderAgentHtml.d.ts +27 -0
  299. package/dist/connectors/telegram/renderAgentHtml.d.ts.map +1 -0
  300. package/dist/connectors/telegram/renderAgentHtml.js +97 -0
  301. package/dist/connectors/telegram/renderAgentHtml.js.map +1 -0
  302. package/dist/connectors/telegram/sessionKeyCodec.d.ts +28 -0
  303. package/dist/connectors/telegram/sessionKeyCodec.d.ts.map +1 -0
  304. package/dist/connectors/telegram/sessionKeyCodec.js +107 -0
  305. package/dist/connectors/telegram/sessionKeyCodec.js.map +1 -0
  306. package/dist/connectors/telegram/telegramCallGuard.d.ts +19 -0
  307. package/dist/connectors/telegram/telegramCallGuard.d.ts.map +1 -0
  308. package/dist/connectors/telegram/telegramCallGuard.js +35 -0
  309. package/dist/connectors/telegram/telegramCallGuard.js.map +1 -0
  310. package/dist/connectors/telegram/timezonePicker.d.ts +85 -0
  311. package/dist/connectors/telegram/timezonePicker.d.ts.map +1 -0
  312. package/dist/connectors/telegram/timezonePicker.js +142 -0
  313. package/dist/connectors/telegram/timezonePicker.js.map +1 -0
  314. package/dist/connectors/telegram/updateDispatcher.d.ts +85 -0
  315. package/dist/connectors/telegram/updateDispatcher.d.ts.map +1 -0
  316. package/dist/connectors/telegram/updateDispatcher.js +159 -0
  317. package/dist/connectors/telegram/updateDispatcher.js.map +1 -0
  318. package/dist/connectors/test/connector.d.ts +52 -0
  319. package/dist/connectors/test/connector.d.ts.map +1 -0
  320. package/dist/connectors/test/connector.js +143 -0
  321. package/dist/connectors/test/connector.js.map +1 -0
  322. package/dist/connectors/test/sessionKeyCodec.d.ts +25 -0
  323. package/dist/connectors/test/sessionKeyCodec.d.ts.map +1 -0
  324. package/dist/connectors/test/sessionKeyCodec.js +50 -0
  325. package/dist/connectors/test/sessionKeyCodec.js.map +1 -0
  326. package/dist/diagLog.d.ts +6 -0
  327. package/dist/diagLog.d.ts.map +1 -0
  328. package/dist/diagLog.js +58 -0
  329. package/dist/diagLog.js.map +1 -0
  330. package/dist/effortLevels.d.ts +68 -0
  331. package/dist/effortLevels.d.ts.map +1 -0
  332. package/dist/effortLevels.js +114 -0
  333. package/dist/effortLevels.js.map +1 -0
  334. package/dist/folderName.d.ts +42 -0
  335. package/dist/folderName.d.ts.map +1 -0
  336. package/dist/folderName.js +51 -0
  337. package/dist/folderName.js.map +1 -0
  338. package/dist/i18n/de.d.ts +2 -0
  339. package/dist/i18n/de.d.ts.map +1 -0
  340. package/dist/i18n/de.js +665 -0
  341. package/dist/i18n/de.js.map +1 -0
  342. package/dist/i18n/en.d.ts +7 -0
  343. package/dist/i18n/en.d.ts.map +1 -0
  344. package/dist/i18n/en.js +694 -0
  345. package/dist/i18n/en.js.map +1 -0
  346. package/dist/i18n/es.d.ts +2 -0
  347. package/dist/i18n/es.d.ts.map +1 -0
  348. package/dist/i18n/es.js +665 -0
  349. package/dist/i18n/es.js.map +1 -0
  350. package/dist/i18n/fr.d.ts +2 -0
  351. package/dist/i18n/fr.d.ts.map +1 -0
  352. package/dist/i18n/fr.js +665 -0
  353. package/dist/i18n/fr.js.map +1 -0
  354. package/dist/i18n/hi.d.ts +2 -0
  355. package/dist/i18n/hi.d.ts.map +1 -0
  356. package/dist/i18n/hi.js +665 -0
  357. package/dist/i18n/hi.js.map +1 -0
  358. package/dist/i18n/ja.d.ts +2 -0
  359. package/dist/i18n/ja.d.ts.map +1 -0
  360. package/dist/i18n/ja.js +665 -0
  361. package/dist/i18n/ja.js.map +1 -0
  362. package/dist/i18n/ka.d.ts +2 -0
  363. package/dist/i18n/ka.d.ts.map +1 -0
  364. package/dist/i18n/ka.js +665 -0
  365. package/dist/i18n/ka.js.map +1 -0
  366. package/dist/i18n/pt.d.ts +2 -0
  367. package/dist/i18n/pt.d.ts.map +1 -0
  368. package/dist/i18n/pt.js +665 -0
  369. package/dist/i18n/pt.js.map +1 -0
  370. package/dist/i18n/ru.d.ts +6 -0
  371. package/dist/i18n/ru.d.ts.map +1 -0
  372. package/dist/i18n/ru.js +668 -0
  373. package/dist/i18n/ru.js.map +1 -0
  374. package/dist/i18n/uk.d.ts +8 -0
  375. package/dist/i18n/uk.d.ts.map +1 -0
  376. package/dist/i18n/uk.js +670 -0
  377. package/dist/i18n/uk.js.map +1 -0
  378. package/dist/i18n/uz.d.ts +2 -0
  379. package/dist/i18n/uz.d.ts.map +1 -0
  380. package/dist/i18n/uz.js +665 -0
  381. package/dist/i18n/uz.js.map +1 -0
  382. package/dist/i18n/zh.d.ts +2 -0
  383. package/dist/i18n/zh.d.ts.map +1 -0
  384. package/dist/i18n/zh.js +665 -0
  385. package/dist/i18n/zh.js.map +1 -0
  386. package/dist/i18n.d.ts +115 -0
  387. package/dist/i18n.d.ts.map +1 -0
  388. package/dist/i18n.js +233 -0
  389. package/dist/i18n.js.map +1 -0
  390. package/dist/index.d.ts +3 -0
  391. package/dist/index.d.ts.map +1 -0
  392. package/dist/index.js +10 -0
  393. package/dist/index.js.map +1 -0
  394. package/dist/installManager.d.ts +99 -0
  395. package/dist/installManager.d.ts.map +1 -0
  396. package/dist/installManager.js +943 -0
  397. package/dist/installManager.js.map +1 -0
  398. package/dist/instanceLock.d.ts +161 -0
  399. package/dist/instanceLock.d.ts.map +1 -0
  400. package/dist/instanceLock.js +388 -0
  401. package/dist/instanceLock.js.map +1 -0
  402. package/dist/mcpConfig.d.ts +76 -0
  403. package/dist/mcpConfig.d.ts.map +1 -0
  404. package/dist/mcpConfig.js +270 -0
  405. package/dist/mcpConfig.js.map +1 -0
  406. package/dist/openCodeOpenQuestion.d.ts +44 -0
  407. package/dist/openCodeOpenQuestion.d.ts.map +1 -0
  408. package/dist/openCodeOpenQuestion.js +64 -0
  409. package/dist/openCodeOpenQuestion.js.map +1 -0
  410. package/dist/openCodeQuestionFlow.d.ts +105 -0
  411. package/dist/openCodeQuestionFlow.d.ts.map +1 -0
  412. package/dist/openCodeQuestionFlow.js +143 -0
  413. package/dist/openCodeQuestionFlow.js.map +1 -0
  414. package/dist/openCodeSessionRouting.d.ts +133 -0
  415. package/dist/openCodeSessionRouting.d.ts.map +1 -0
  416. package/dist/openCodeSessionRouting.js +253 -0
  417. package/dist/openCodeSessionRouting.js.map +1 -0
  418. package/dist/openCodeSessionTitle.d.ts +50 -0
  419. package/dist/openCodeSessionTitle.d.ts.map +1 -0
  420. package/dist/openCodeSessionTitle.js +84 -0
  421. package/dist/openCodeSessionTitle.js.map +1 -0
  422. package/dist/outputTrace.d.ts +108 -0
  423. package/dist/outputTrace.d.ts.map +1 -0
  424. package/dist/outputTrace.js +430 -0
  425. package/dist/outputTrace.js.map +1 -0
  426. package/dist/pendingQuestionRepost.d.ts +47 -0
  427. package/dist/pendingQuestionRepost.d.ts.map +1 -0
  428. package/dist/pendingQuestionRepost.js +29 -0
  429. package/dist/pendingQuestionRepost.js.map +1 -0
  430. package/dist/pinnedStatus.d.ts +52 -0
  431. package/dist/pinnedStatus.d.ts.map +1 -0
  432. package/dist/pinnedStatus.js +36 -0
  433. package/dist/pinnedStatus.js.map +1 -0
  434. package/dist/platform/answerSink.d.ts +93 -0
  435. package/dist/platform/answerSink.d.ts.map +1 -0
  436. package/dist/platform/answerSink.js +31 -0
  437. package/dist/platform/answerSink.js.map +1 -0
  438. package/dist/platform/capabilityFallback.d.ts +39 -0
  439. package/dist/platform/capabilityFallback.d.ts.map +1 -0
  440. package/dist/platform/capabilityFallback.js +55 -0
  441. package/dist/platform/capabilityFallback.js.map +1 -0
  442. package/dist/platform/commandRouter.d.ts +51 -0
  443. package/dist/platform/commandRouter.d.ts.map +1 -0
  444. package/dist/platform/commandRouter.js +53 -0
  445. package/dist/platform/commandRouter.js.map +1 -0
  446. package/dist/platform/connectorSet.d.ts +39 -0
  447. package/dist/platform/connectorSet.d.ts.map +1 -0
  448. package/dist/platform/connectorSet.js +55 -0
  449. package/dist/platform/connectorSet.js.map +1 -0
  450. package/dist/platform/inbound.d.ts +147 -0
  451. package/dist/platform/inbound.d.ts.map +1 -0
  452. package/dist/platform/inbound.js +18 -0
  453. package/dist/platform/inbound.js.map +1 -0
  454. package/dist/platform/outbound.d.ts +225 -0
  455. package/dist/platform/outbound.d.ts.map +1 -0
  456. package/dist/platform/outbound.js +30 -0
  457. package/dist/platform/outbound.js.map +1 -0
  458. package/dist/platform/unservedStateGuard.d.ts +27 -0
  459. package/dist/platform/unservedStateGuard.d.ts.map +1 -0
  460. package/dist/platform/unservedStateGuard.js +42 -0
  461. package/dist/platform/unservedStateGuard.js.map +1 -0
  462. package/dist/postToSession.d.ts +94 -0
  463. package/dist/postToSession.d.ts.map +1 -0
  464. package/dist/postToSession.js +56 -0
  465. package/dist/postToSession.js.map +1 -0
  466. package/dist/progressLine.d.ts +180 -0
  467. package/dist/progressLine.d.ts.map +1 -0
  468. package/dist/progressLine.js +293 -0
  469. package/dist/progressLine.js.map +1 -0
  470. package/dist/rateLimiter.d.ts +246 -0
  471. package/dist/rateLimiter.d.ts.map +1 -0
  472. package/dist/rateLimiter.js +555 -0
  473. package/dist/rateLimiter.js.map +1 -0
  474. package/dist/requests/answerRequest.d.ts +57 -0
  475. package/dist/requests/answerRequest.d.ts.map +1 -0
  476. package/dist/requests/answerRequest.js +96 -0
  477. package/dist/requests/answerRequest.js.map +1 -0
  478. package/dist/requests/answerTail.d.ts +83 -0
  479. package/dist/requests/answerTail.d.ts.map +1 -0
  480. package/dist/requests/answerTail.js +102 -0
  481. package/dist/requests/answerTail.js.map +1 -0
  482. package/dist/requests/limitWaitAnswer.d.ts +87 -0
  483. package/dist/requests/limitWaitAnswer.d.ts.map +1 -0
  484. package/dist/requests/limitWaitAnswer.js +83 -0
  485. package/dist/requests/limitWaitAnswer.js.map +1 -0
  486. package/dist/requests/requestGroup.d.ts +44 -0
  487. package/dist/requests/requestGroup.d.ts.map +1 -0
  488. package/dist/requests/requestGroup.js +63 -0
  489. package/dist/requests/requestGroup.js.map +1 -0
  490. package/dist/requests/requestHeader.d.ts +70 -0
  491. package/dist/requests/requestHeader.d.ts.map +1 -0
  492. package/dist/requests/requestHeader.js +83 -0
  493. package/dist/requests/requestHeader.js.map +1 -0
  494. package/dist/requests/requestLedger.d.ts +217 -0
  495. package/dist/requests/requestLedger.d.ts.map +1 -0
  496. package/dist/requests/requestLedger.js +468 -0
  497. package/dist/requests/requestLedger.js.map +1 -0
  498. package/dist/requests/sessionTurnProbe.d.ts +52 -0
  499. package/dist/requests/sessionTurnProbe.d.ts.map +1 -0
  500. package/dist/requests/sessionTurnProbe.js +29 -0
  501. package/dist/requests/sessionTurnProbe.js.map +1 -0
  502. package/dist/requests/types.d.ts +181 -0
  503. package/dist/requests/types.d.ts.map +1 -0
  504. package/dist/requests/types.js +9 -0
  505. package/dist/requests/types.js.map +1 -0
  506. package/dist/requests/wakeUpEngine.d.ts +160 -0
  507. package/dist/requests/wakeUpEngine.d.ts.map +1 -0
  508. package/dist/requests/wakeUpEngine.js +399 -0
  509. package/dist/requests/wakeUpEngine.js.map +1 -0
  510. package/dist/requests/wakeUpRules.d.ts +151 -0
  511. package/dist/requests/wakeUpRules.d.ts.map +1 -0
  512. package/dist/requests/wakeUpRules.js +167 -0
  513. package/dist/requests/wakeUpRules.js.map +1 -0
  514. package/dist/resumeContext.d.ts +87 -0
  515. package/dist/resumeContext.d.ts.map +1 -0
  516. package/dist/resumeContext.js +114 -0
  517. package/dist/resumeContext.js.map +1 -0
  518. package/dist/scheduler/checkRun.d.ts +95 -0
  519. package/dist/scheduler/checkRun.d.ts.map +1 -0
  520. package/dist/scheduler/checkRun.js +172 -0
  521. package/dist/scheduler/checkRun.js.map +1 -0
  522. package/dist/scheduler/delivery.d.ts +99 -0
  523. package/dist/scheduler/delivery.d.ts.map +1 -0
  524. package/dist/scheduler/delivery.js +212 -0
  525. package/dist/scheduler/delivery.js.map +1 -0
  526. package/dist/scheduler/deliveryKind.d.ts +29 -0
  527. package/dist/scheduler/deliveryKind.d.ts.map +1 -0
  528. package/dist/scheduler/deliveryKind.js +39 -0
  529. package/dist/scheduler/deliveryKind.js.map +1 -0
  530. package/dist/scheduler/directoryThreads.d.ts +19 -0
  531. package/dist/scheduler/directoryThreads.d.ts.map +1 -0
  532. package/dist/scheduler/directoryThreads.js +28 -0
  533. package/dist/scheduler/directoryThreads.js.map +1 -0
  534. package/dist/scheduler/engine.d.ts +77 -0
  535. package/dist/scheduler/engine.d.ts.map +1 -0
  536. package/dist/scheduler/engine.js +337 -0
  537. package/dist/scheduler/engine.js.map +1 -0
  538. package/dist/scheduler/injection.d.ts +102 -0
  539. package/dist/scheduler/injection.d.ts.map +1 -0
  540. package/dist/scheduler/injection.js +122 -0
  541. package/dist/scheduler/injection.js.map +1 -0
  542. package/dist/scheduler/mcpBoot.d.ts +65 -0
  543. package/dist/scheduler/mcpBoot.d.ts.map +1 -0
  544. package/dist/scheduler/mcpBoot.js +60 -0
  545. package/dist/scheduler/mcpBoot.js.map +1 -0
  546. package/dist/scheduler/mcpSurface.d.ts +363 -0
  547. package/dist/scheduler/mcpSurface.d.ts.map +1 -0
  548. package/dist/scheduler/mcpSurface.js +1297 -0
  549. package/dist/scheduler/mcpSurface.js.map +1 -0
  550. package/dist/scheduler/rebindResume.d.ts +27 -0
  551. package/dist/scheduler/rebindResume.d.ts.map +1 -0
  552. package/dist/scheduler/rebindResume.js +17 -0
  553. package/dist/scheduler/rebindResume.js.map +1 -0
  554. package/dist/scheduler/recurrence.d.ts +58 -0
  555. package/dist/scheduler/recurrence.d.ts.map +1 -0
  556. package/dist/scheduler/recurrence.js +210 -0
  557. package/dist/scheduler/recurrence.js.map +1 -0
  558. package/dist/scheduler/runLedger.d.ts +59 -0
  559. package/dist/scheduler/runLedger.d.ts.map +1 -0
  560. package/dist/scheduler/runLedger.js +54 -0
  561. package/dist/scheduler/runLedger.js.map +1 -0
  562. package/dist/scheduler/store.d.ts +97 -0
  563. package/dist/scheduler/store.d.ts.map +1 -0
  564. package/dist/scheduler/store.js +124 -0
  565. package/dist/scheduler/store.js.map +1 -0
  566. package/dist/scheduler/timezoneRecompute.d.ts +61 -0
  567. package/dist/scheduler/timezoneRecompute.d.ts.map +1 -0
  568. package/dist/scheduler/timezoneRecompute.js +46 -0
  569. package/dist/scheduler/timezoneRecompute.js.map +1 -0
  570. package/dist/scheduler/types.d.ts +139 -0
  571. package/dist/scheduler/types.d.ts.map +1 -0
  572. package/dist/scheduler/types.js +8 -0
  573. package/dist/scheduler/types.js.map +1 -0
  574. package/dist/sendErrorClassifier.d.ts +45 -0
  575. package/dist/sendErrorClassifier.d.ts.map +1 -0
  576. package/dist/sendErrorClassifier.js +65 -0
  577. package/dist/sendErrorClassifier.js.map +1 -0
  578. package/dist/sessionKey.d.ts +136 -0
  579. package/dist/sessionKey.d.ts.map +1 -0
  580. package/dist/sessionKey.js +153 -0
  581. package/dist/sessionKey.js.map +1 -0
  582. package/dist/sessionPick.d.ts +42 -0
  583. package/dist/sessionPick.d.ts.map +1 -0
  584. package/dist/sessionPick.js +39 -0
  585. package/dist/sessionPick.js.map +1 -0
  586. package/dist/shutdown.d.ts +126 -0
  587. package/dist/shutdown.d.ts.map +1 -0
  588. package/dist/shutdown.js +164 -0
  589. package/dist/shutdown.js.map +1 -0
  590. package/dist/startupPromptBuffer.d.ts +85 -0
  591. package/dist/startupPromptBuffer.d.ts.map +1 -0
  592. package/dist/startupPromptBuffer.js +123 -0
  593. package/dist/startupPromptBuffer.js.map +1 -0
  594. package/dist/state.d.ts +1416 -0
  595. package/dist/state.d.ts.map +1 -0
  596. package/dist/state.js +2128 -0
  597. package/dist/state.js.map +1 -0
  598. package/dist/threadContextPreamble.d.ts +89 -0
  599. package/dist/threadContextPreamble.d.ts.map +1 -0
  600. package/dist/threadContextPreamble.js +102 -0
  601. package/dist/threadContextPreamble.js.map +1 -0
  602. package/dist/threadRouting.d.ts +164 -0
  603. package/dist/threadRouting.d.ts.map +1 -0
  604. package/dist/threadRouting.js +182 -0
  605. package/dist/threadRouting.js.map +1 -0
  606. package/dist/types.d.ts +1022 -0
  607. package/dist/types.d.ts.map +1 -0
  608. package/dist/types.js +3 -0
  609. package/dist/types.js.map +1 -0
  610. package/dist/utils/abortableFifo.d.ts +10 -0
  611. package/dist/utils/abortableFifo.d.ts.map +1 -0
  612. package/dist/utils/abortableFifo.js +55 -0
  613. package/dist/utils/abortableFifo.js.map +1 -0
  614. package/dist/utils/adoptedToolList.d.ts +27 -0
  615. package/dist/utils/adoptedToolList.d.ts.map +1 -0
  616. package/dist/utils/adoptedToolList.js +22 -0
  617. package/dist/utils/adoptedToolList.js.map +1 -0
  618. package/dist/utils/agentEnvironment.d.ts +42 -0
  619. package/dist/utils/agentEnvironment.d.ts.map +1 -0
  620. package/dist/utils/agentEnvironment.js +122 -0
  621. package/dist/utils/agentEnvironment.js.map +1 -0
  622. package/dist/utils/ansiClean.d.ts +11 -0
  623. package/dist/utils/ansiClean.d.ts.map +1 -0
  624. package/dist/utils/ansiClean.js +139 -0
  625. package/dist/utils/ansiClean.js.map +1 -0
  626. package/dist/utils/autoContinueOnLimit.d.ts +56 -0
  627. package/dist/utils/autoContinueOnLimit.d.ts.map +1 -0
  628. package/dist/utils/autoContinueOnLimit.js +79 -0
  629. package/dist/utils/autoContinueOnLimit.js.map +1 -0
  630. package/dist/utils/canonicalPathContainment.d.ts +7 -0
  631. package/dist/utils/canonicalPathContainment.d.ts.map +1 -0
  632. package/dist/utils/canonicalPathContainment.js +48 -0
  633. package/dist/utils/canonicalPathContainment.js.map +1 -0
  634. package/dist/utils/claudeAccountPickerPlan.d.ts +85 -0
  635. package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -0
  636. package/dist/utils/claudeAccountPickerPlan.js +123 -0
  637. package/dist/utils/claudeAccountPickerPlan.js.map +1 -0
  638. package/dist/utils/claudeAuthLogin.d.ts +71 -0
  639. package/dist/utils/claudeAuthLogin.d.ts.map +1 -0
  640. package/dist/utils/claudeAuthLogin.js +93 -0
  641. package/dist/utils/claudeAuthLogin.js.map +1 -0
  642. package/dist/utils/claudeChunkClassifier.d.ts +97 -0
  643. package/dist/utils/claudeChunkClassifier.d.ts.map +1 -0
  644. package/dist/utils/claudeChunkClassifier.js +254 -0
  645. package/dist/utils/claudeChunkClassifier.js.map +1 -0
  646. package/dist/utils/claudeCodeVersion.d.ts +31 -0
  647. package/dist/utils/claudeCodeVersion.d.ts.map +1 -0
  648. package/dist/utils/claudeCodeVersion.js +62 -0
  649. package/dist/utils/claudeCodeVersion.js.map +1 -0
  650. package/dist/utils/claudeCompactHook.d.ts +64 -0
  651. package/dist/utils/claudeCompactHook.d.ts.map +1 -0
  652. package/dist/utils/claudeCompactHook.js +98 -0
  653. package/dist/utils/claudeCompactHook.js.map +1 -0
  654. package/dist/utils/claudeLimitSwitch.d.ts +40 -0
  655. package/dist/utils/claudeLimitSwitch.d.ts.map +1 -0
  656. package/dist/utils/claudeLimitSwitch.js +54 -0
  657. package/dist/utils/claudeLimitSwitch.js.map +1 -0
  658. package/dist/utils/claudeLivenessAction.d.ts +208 -0
  659. package/dist/utils/claudeLivenessAction.d.ts.map +1 -0
  660. package/dist/utils/claudeLivenessAction.js +135 -0
  661. package/dist/utils/claudeLivenessAction.js.map +1 -0
  662. package/dist/utils/claudeMcpHeal.d.ts +92 -0
  663. package/dist/utils/claudeMcpHeal.d.ts.map +1 -0
  664. package/dist/utils/claudeMcpHeal.js +91 -0
  665. package/dist/utils/claudeMcpHeal.js.map +1 -0
  666. package/dist/utils/claudeProfileUsageLines.d.ts +15 -0
  667. package/dist/utils/claudeProfileUsageLines.d.ts.map +1 -0
  668. package/dist/utils/claudeProfileUsageLines.js +42 -0
  669. package/dist/utils/claudeProfileUsageLines.js.map +1 -0
  670. package/dist/utils/claudeProfiles.d.ts +117 -0
  671. package/dist/utils/claudeProfiles.d.ts.map +1 -0
  672. package/dist/utils/claudeProfiles.js +285 -0
  673. package/dist/utils/claudeProfiles.js.map +1 -0
  674. package/dist/utils/claudeRelayRouting.d.ts +86 -0
  675. package/dist/utils/claudeRelayRouting.d.ts.map +1 -0
  676. package/dist/utils/claudeRelayRouting.js +159 -0
  677. package/dist/utils/claudeRelayRouting.js.map +1 -0
  678. package/dist/utils/claudeRuntimeInfo.d.ts +26 -0
  679. package/dist/utils/claudeRuntimeInfo.d.ts.map +1 -0
  680. package/dist/utils/claudeRuntimeInfo.js +151 -0
  681. package/dist/utils/claudeRuntimeInfo.js.map +1 -0
  682. package/dist/utils/claudeScrapeShapes.d.ts +256 -0
  683. package/dist/utils/claudeScrapeShapes.d.ts.map +1 -0
  684. package/dist/utils/claudeScrapeShapes.js +290 -0
  685. package/dist/utils/claudeScrapeShapes.js.map +1 -0
  686. package/dist/utils/claudeSessionTitle.d.ts +27 -0
  687. package/dist/utils/claudeSessionTitle.d.ts.map +1 -0
  688. package/dist/utils/claudeSessionTitle.js +90 -0
  689. package/dist/utils/claudeSessionTitle.js.map +1 -0
  690. package/dist/utils/claudeStreamJson.d.ts +214 -0
  691. package/dist/utils/claudeStreamJson.d.ts.map +1 -0
  692. package/dist/utils/claudeStreamJson.js +364 -0
  693. package/dist/utils/claudeStreamJson.js.map +1 -0
  694. package/dist/utils/claudeSubagentTail.d.ts +93 -0
  695. package/dist/utils/claudeSubagentTail.d.ts.map +1 -0
  696. package/dist/utils/claudeSubagentTail.js +136 -0
  697. package/dist/utils/claudeSubagentTail.js.map +1 -0
  698. package/dist/utils/claudeUsage.d.ts +209 -0
  699. package/dist/utils/claudeUsage.d.ts.map +1 -0
  700. package/dist/utils/claudeUsage.js +413 -0
  701. package/dist/utils/claudeUsage.js.map +1 -0
  702. package/dist/utils/claudeUsageRefresher.d.ts +44 -0
  703. package/dist/utils/claudeUsageRefresher.d.ts.map +1 -0
  704. package/dist/utils/claudeUsageRefresher.js +95 -0
  705. package/dist/utils/claudeUsageRefresher.js.map +1 -0
  706. package/dist/utils/clearThreadOutputQueues.d.ts +43 -0
  707. package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -0
  708. package/dist/utils/clearThreadOutputQueues.js +39 -0
  709. package/dist/utils/clearThreadOutputQueues.js.map +1 -0
  710. package/dist/utils/compactCommandRoute.d.ts +45 -0
  711. package/dist/utils/compactCommandRoute.d.ts.map +1 -0
  712. package/dist/utils/compactCommandRoute.js +36 -0
  713. package/dist/utils/compactCommandRoute.js.map +1 -0
  714. package/dist/utils/compactOnIdle.d.ts +329 -0
  715. package/dist/utils/compactOnIdle.d.ts.map +1 -0
  716. package/dist/utils/compactOnIdle.js +379 -0
  717. package/dist/utils/compactOnIdle.js.map +1 -0
  718. package/dist/utils/consoleFileTap.d.ts +51 -0
  719. package/dist/utils/consoleFileTap.d.ts.map +1 -0
  720. package/dist/utils/consoleFileTap.js +86 -0
  721. package/dist/utils/consoleFileTap.js.map +1 -0
  722. package/dist/utils/displayVerbosity.d.ts +44 -0
  723. package/dist/utils/displayVerbosity.d.ts.map +1 -0
  724. package/dist/utils/displayVerbosity.js +75 -0
  725. package/dist/utils/displayVerbosity.js.map +1 -0
  726. package/dist/utils/download.d.ts +72 -0
  727. package/dist/utils/download.d.ts.map +1 -0
  728. package/dist/utils/download.js +216 -0
  729. package/dist/utils/download.js.map +1 -0
  730. package/dist/utils/draftFinalize.d.ts +110 -0
  731. package/dist/utils/draftFinalize.d.ts.map +1 -0
  732. package/dist/utils/draftFinalize.js +100 -0
  733. package/dist/utils/draftFinalize.js.map +1 -0
  734. package/dist/utils/draftId.d.ts +36 -0
  735. package/dist/utils/draftId.d.ts.map +1 -0
  736. package/dist/utils/draftId.js +46 -0
  737. package/dist/utils/draftId.js.map +1 -0
  738. package/dist/utils/draftPacer.d.ts +72 -0
  739. package/dist/utils/draftPacer.d.ts.map +1 -0
  740. package/dist/utils/draftPacer.js +53 -0
  741. package/dist/utils/draftPacer.js.map +1 -0
  742. package/dist/utils/effortStartupKeystroke.d.ts +20 -0
  743. package/dist/utils/effortStartupKeystroke.d.ts.map +1 -0
  744. package/dist/utils/effortStartupKeystroke.js +28 -0
  745. package/dist/utils/effortStartupKeystroke.js.map +1 -0
  746. package/dist/utils/ensureSessionPlan.d.ts +70 -0
  747. package/dist/utils/ensureSessionPlan.d.ts.map +1 -0
  748. package/dist/utils/ensureSessionPlan.js +54 -0
  749. package/dist/utils/ensureSessionPlan.js.map +1 -0
  750. package/dist/utils/fallbackBridge.d.ts +32 -0
  751. package/dist/utils/fallbackBridge.d.ts.map +1 -0
  752. package/dist/utils/fallbackBridge.js +62 -0
  753. package/dist/utils/fallbackBridge.js.map +1 -0
  754. package/dist/utils/fallbackChain.d.ts +299 -0
  755. package/dist/utils/fallbackChain.d.ts.map +1 -0
  756. package/dist/utils/fallbackChain.js +542 -0
  757. package/dist/utils/fallbackChain.js.map +1 -0
  758. package/dist/utils/fileSendPlan.d.ts +217 -0
  759. package/dist/utils/fileSendPlan.d.ts.map +1 -0
  760. package/dist/utils/fileSendPlan.js +291 -0
  761. package/dist/utils/fileSendPlan.js.map +1 -0
  762. package/dist/utils/fileSendService.d.ts +94 -0
  763. package/dist/utils/fileSendService.d.ts.map +1 -0
  764. package/dist/utils/fileSendService.js +387 -0
  765. package/dist/utils/fileSendService.js.map +1 -0
  766. package/dist/utils/groupFinalizePlan.d.ts +38 -0
  767. package/dist/utils/groupFinalizePlan.d.ts.map +1 -0
  768. package/dist/utils/groupFinalizePlan.js +35 -0
  769. package/dist/utils/groupFinalizePlan.js.map +1 -0
  770. package/dist/utils/identicalOutputGuard.d.ts +83 -0
  771. package/dist/utils/identicalOutputGuard.d.ts.map +1 -0
  772. package/dist/utils/identicalOutputGuard.js +99 -0
  773. package/dist/utils/identicalOutputGuard.js.map +1 -0
  774. package/dist/utils/isoTimestamp.d.ts +16 -0
  775. package/dist/utils/isoTimestamp.d.ts.map +1 -0
  776. package/dist/utils/isoTimestamp.js +34 -0
  777. package/dist/utils/isoTimestamp.js.map +1 -0
  778. package/dist/utils/jsonStreamBusyWatchdog.d.ts +130 -0
  779. package/dist/utils/jsonStreamBusyWatchdog.d.ts.map +1 -0
  780. package/dist/utils/jsonStreamBusyWatchdog.js +122 -0
  781. package/dist/utils/jsonStreamBusyWatchdog.js.map +1 -0
  782. package/dist/utils/jsonStreamHost.d.ts +238 -0
  783. package/dist/utils/jsonStreamHost.d.ts.map +1 -0
  784. package/dist/utils/jsonStreamHost.js +438 -0
  785. package/dist/utils/jsonStreamHost.js.map +1 -0
  786. package/dist/utils/keyedTransitionQueue.d.ts +18 -0
  787. package/dist/utils/keyedTransitionQueue.d.ts.map +1 -0
  788. package/dist/utils/keyedTransitionQueue.js +34 -0
  789. package/dist/utils/keyedTransitionQueue.js.map +1 -0
  790. package/dist/utils/limitEpisodeRecovery.d.ts +92 -0
  791. package/dist/utils/limitEpisodeRecovery.d.ts.map +1 -0
  792. package/dist/utils/limitEpisodeRecovery.js +133 -0
  793. package/dist/utils/limitEpisodeRecovery.js.map +1 -0
  794. package/dist/utils/limitHeldPromptQueue.d.ts +48 -0
  795. package/dist/utils/limitHeldPromptQueue.d.ts.map +1 -0
  796. package/dist/utils/limitHeldPromptQueue.js +59 -0
  797. package/dist/utils/limitHeldPromptQueue.js.map +1 -0
  798. package/dist/utils/limitHeldPrompts.d.ts +58 -0
  799. package/dist/utils/limitHeldPrompts.d.ts.map +1 -0
  800. package/dist/utils/limitHeldPrompts.js +65 -0
  801. package/dist/utils/limitHeldPrompts.js.map +1 -0
  802. package/dist/utils/linkPreviewSuppression.d.ts +18 -0
  803. package/dist/utils/linkPreviewSuppression.d.ts.map +1 -0
  804. package/dist/utils/linkPreviewSuppression.js +37 -0
  805. package/dist/utils/linkPreviewSuppression.js.map +1 -0
  806. package/dist/utils/localClock.d.ts +15 -0
  807. package/dist/utils/localClock.d.ts.map +1 -0
  808. package/dist/utils/localClock.js +29 -0
  809. package/dist/utils/localClock.js.map +1 -0
  810. package/dist/utils/markdownTableRender.d.ts +83 -0
  811. package/dist/utils/markdownTableRender.d.ts.map +1 -0
  812. package/dist/utils/markdownTableRender.js +298 -0
  813. package/dist/utils/markdownTableRender.js.map +1 -0
  814. package/dist/utils/mediaGroupCollector.d.ts +51 -0
  815. package/dist/utils/mediaGroupCollector.d.ts.map +1 -0
  816. package/dist/utils/mediaGroupCollector.js +90 -0
  817. package/dist/utils/mediaGroupCollector.js.map +1 -0
  818. package/dist/utils/messageSendService.d.ts +136 -0
  819. package/dist/utils/messageSendService.d.ts.map +1 -0
  820. package/dist/utils/messageSendService.js +165 -0
  821. package/dist/utils/messageSendService.js.map +1 -0
  822. package/dist/utils/minutesOverride.d.ts +7 -0
  823. package/dist/utils/minutesOverride.d.ts.map +1 -0
  824. package/dist/utils/minutesOverride.js +16 -0
  825. package/dist/utils/minutesOverride.js.map +1 -0
  826. package/dist/utils/modelPickerPlan.d.ts +119 -0
  827. package/dist/utils/modelPickerPlan.d.ts.map +1 -0
  828. package/dist/utils/modelPickerPlan.js +171 -0
  829. package/dist/utils/modelPickerPlan.js.map +1 -0
  830. package/dist/utils/modelRecentPlan.d.ts +56 -0
  831. package/dist/utils/modelRecentPlan.d.ts.map +1 -0
  832. package/dist/utils/modelRecentPlan.js +107 -0
  833. package/dist/utils/modelRecentPlan.js.map +1 -0
  834. package/dist/utils/modelSetReplyDecision.d.ts +67 -0
  835. package/dist/utils/modelSetReplyDecision.d.ts.map +1 -0
  836. package/dist/utils/modelSetReplyDecision.js +27 -0
  837. package/dist/utils/modelSetReplyDecision.js.map +1 -0
  838. package/dist/utils/openCodeAuthLogin.d.ts +181 -0
  839. package/dist/utils/openCodeAuthLogin.d.ts.map +1 -0
  840. package/dist/utils/openCodeAuthLogin.js +393 -0
  841. package/dist/utils/openCodeAuthLogin.js.map +1 -0
  842. package/dist/utils/openCodeCompactPlugin.d.ts +81 -0
  843. package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -0
  844. package/dist/utils/openCodeCompactPlugin.js +146 -0
  845. package/dist/utils/openCodeCompactPlugin.js.map +1 -0
  846. package/dist/utils/openCodeTurnActivity.d.ts +67 -0
  847. package/dist/utils/openCodeTurnActivity.d.ts.map +1 -0
  848. package/dist/utils/openCodeTurnActivity.js +27 -0
  849. package/dist/utils/openCodeTurnActivity.js.map +1 -0
  850. package/dist/utils/outputDebounce.d.ts +24 -0
  851. package/dist/utils/outputDebounce.d.ts.map +1 -0
  852. package/dist/utils/outputDebounce.js +30 -0
  853. package/dist/utils/outputDebounce.js.map +1 -0
  854. package/dist/utils/outputFlushTiming.d.ts +54 -0
  855. package/dist/utils/outputFlushTiming.d.ts.map +1 -0
  856. package/dist/utils/outputFlushTiming.js +48 -0
  857. package/dist/utils/outputFlushTiming.js.map +1 -0
  858. package/dist/utils/paginateList.d.ts +25 -0
  859. package/dist/utils/paginateList.d.ts.map +1 -0
  860. package/dist/utils/paginateList.js +32 -0
  861. package/dist/utils/paginateList.js.map +1 -0
  862. package/dist/utils/paneDiff.d.ts +55 -0
  863. package/dist/utils/paneDiff.d.ts.map +1 -0
  864. package/dist/utils/paneDiff.js +88 -0
  865. package/dist/utils/paneDiff.js.map +1 -0
  866. package/dist/utils/paneResizeGuard.d.ts +69 -0
  867. package/dist/utils/paneResizeGuard.d.ts.map +1 -0
  868. package/dist/utils/paneResizeGuard.js +91 -0
  869. package/dist/utils/paneResizeGuard.js.map +1 -0
  870. package/dist/utils/persistAdapterSessionIds.d.ts +39 -0
  871. package/dist/utils/persistAdapterSessionIds.d.ts.map +1 -0
  872. package/dist/utils/persistAdapterSessionIds.js +28 -0
  873. package/dist/utils/persistAdapterSessionIds.js.map +1 -0
  874. package/dist/utils/pinnedBannerSkipDecision.d.ts +44 -0
  875. package/dist/utils/pinnedBannerSkipDecision.d.ts.map +1 -0
  876. package/dist/utils/pinnedBannerSkipDecision.js +20 -0
  877. package/dist/utils/pinnedBannerSkipDecision.js.map +1 -0
  878. package/dist/utils/pollBackoff.d.ts +45 -0
  879. package/dist/utils/pollBackoff.d.ts.map +1 -0
  880. package/dist/utils/pollBackoff.js +44 -0
  881. package/dist/utils/pollBackoff.js.map +1 -0
  882. package/dist/utils/promptDelivery.d.ts +46 -0
  883. package/dist/utils/promptDelivery.d.ts.map +1 -0
  884. package/dist/utils/promptDelivery.js +49 -0
  885. package/dist/utils/promptDelivery.js.map +1 -0
  886. package/dist/utils/providerDisconnectPlan.d.ts +82 -0
  887. package/dist/utils/providerDisconnectPlan.d.ts.map +1 -0
  888. package/dist/utils/providerDisconnectPlan.js +110 -0
  889. package/dist/utils/providerDisconnectPlan.js.map +1 -0
  890. package/dist/utils/rateLimitLog.d.ts +48 -0
  891. package/dist/utils/rateLimitLog.d.ts.map +1 -0
  892. package/dist/utils/rateLimitLog.js +43 -0
  893. package/dist/utils/rateLimitLog.js.map +1 -0
  894. package/dist/utils/recentRelayWindow.d.ts +165 -0
  895. package/dist/utils/recentRelayWindow.d.ts.map +1 -0
  896. package/dist/utils/recentRelayWindow.js +284 -0
  897. package/dist/utils/recentRelayWindow.js.map +1 -0
  898. package/dist/utils/recvPreviewRedaction.d.ts +23 -0
  899. package/dist/utils/recvPreviewRedaction.d.ts.map +1 -0
  900. package/dist/utils/recvPreviewRedaction.js +39 -0
  901. package/dist/utils/recvPreviewRedaction.js.map +1 -0
  902. package/dist/utils/reminderFlow.d.ts +148 -0
  903. package/dist/utils/reminderFlow.d.ts.map +1 -0
  904. package/dist/utils/reminderFlow.js +122 -0
  905. package/dist/utils/reminderFlow.js.map +1 -0
  906. package/dist/utils/reminderScheduleText.d.ts +87 -0
  907. package/dist/utils/reminderScheduleText.d.ts.map +1 -0
  908. package/dist/utils/reminderScheduleText.js +135 -0
  909. package/dist/utils/reminderScheduleText.js.map +1 -0
  910. package/dist/utils/reminderWizard.d.ts +710 -0
  911. package/dist/utils/reminderWizard.d.ts.map +1 -0
  912. package/dist/utils/reminderWizard.js +1164 -0
  913. package/dist/utils/reminderWizard.js.map +1 -0
  914. package/dist/utils/replyQuote.d.ts +111 -0
  915. package/dist/utils/replyQuote.d.ts.map +1 -0
  916. package/dist/utils/replyQuote.js +122 -0
  917. package/dist/utils/replyQuote.js.map +1 -0
  918. package/dist/utils/resolveBinary.d.ts +25 -0
  919. package/dist/utils/resolveBinary.d.ts.map +1 -0
  920. package/dist/utils/resolveBinary.js +99 -0
  921. package/dist/utils/resolveBinary.js.map +1 -0
  922. package/dist/utils/retryAfterHeader.d.ts +7 -0
  923. package/dist/utils/retryAfterHeader.d.ts.map +1 -0
  924. package/dist/utils/retryAfterHeader.js +17 -0
  925. package/dist/utils/retryAfterHeader.js.map +1 -0
  926. package/dist/utils/rotatingJsonlFile.d.ts +40 -0
  927. package/dist/utils/rotatingJsonlFile.d.ts.map +1 -0
  928. package/dist/utils/rotatingJsonlFile.js +106 -0
  929. package/dist/utils/rotatingJsonlFile.js.map +1 -0
  930. package/dist/utils/rotatingLogFile.d.ts +25 -0
  931. package/dist/utils/rotatingLogFile.d.ts.map +1 -0
  932. package/dist/utils/rotatingLogFile.js +74 -0
  933. package/dist/utils/rotatingLogFile.js.map +1 -0
  934. package/dist/utils/sendRateTracker.d.ts +59 -0
  935. package/dist/utils/sendRateTracker.d.ts.map +1 -0
  936. package/dist/utils/sendRateTracker.js +144 -0
  937. package/dist/utils/sendRateTracker.js.map +1 -0
  938. package/dist/utils/serialQueue.d.ts +5 -0
  939. package/dist/utils/serialQueue.d.ts.map +1 -0
  940. package/dist/utils/serialQueue.js +14 -0
  941. package/dist/utils/serialQueue.js.map +1 -0
  942. package/dist/utils/sseStreamLifecycle.d.ts +32 -0
  943. package/dist/utils/sseStreamLifecycle.d.ts.map +1 -0
  944. package/dist/utils/sseStreamLifecycle.js +41 -0
  945. package/dist/utils/sseStreamLifecycle.js.map +1 -0
  946. package/dist/utils/startupReadiness.d.ts +119 -0
  947. package/dist/utils/startupReadiness.d.ts.map +1 -0
  948. package/dist/utils/startupReadiness.js +148 -0
  949. package/dist/utils/startupReadiness.js.map +1 -0
  950. package/dist/utils/statusFlushDecision.d.ts +18 -0
  951. package/dist/utils/statusFlushDecision.d.ts.map +1 -0
  952. package/dist/utils/statusFlushDecision.js +53 -0
  953. package/dist/utils/statusFlushDecision.js.map +1 -0
  954. package/dist/utils/subagentRender.d.ts +52 -0
  955. package/dist/utils/subagentRender.d.ts.map +1 -0
  956. package/dist/utils/subagentRender.js +66 -0
  957. package/dist/utils/subagentRender.js.map +1 -0
  958. package/dist/utils/subagentStatusRender.d.ts +90 -0
  959. package/dist/utils/subagentStatusRender.d.ts.map +1 -0
  960. package/dist/utils/subagentStatusRender.js +124 -0
  961. package/dist/utils/subagentStatusRender.js.map +1 -0
  962. package/dist/utils/telegramError.d.ts +2 -0
  963. package/dist/utils/telegramError.d.ts.map +1 -0
  964. package/dist/utils/telegramError.js +16 -0
  965. package/dist/utils/telegramError.js.map +1 -0
  966. package/dist/utils/terminalEmitPlan.d.ts +74 -0
  967. package/dist/utils/terminalEmitPlan.d.ts.map +1 -0
  968. package/dist/utils/terminalEmitPlan.js +86 -0
  969. package/dist/utils/terminalEmitPlan.js.map +1 -0
  970. package/dist/utils/thinkingRender.d.ts +74 -0
  971. package/dist/utils/thinkingRender.d.ts.map +1 -0
  972. package/dist/utils/thinkingRender.js +85 -0
  973. package/dist/utils/thinkingRender.js.map +1 -0
  974. package/dist/utils/threadStatusReport.d.ts +79 -0
  975. package/dist/utils/threadStatusReport.d.ts.map +1 -0
  976. package/dist/utils/threadStatusReport.js +88 -0
  977. package/dist/utils/threadStatusReport.js.map +1 -0
  978. package/dist/utils/threadToggle.d.ts +29 -0
  979. package/dist/utils/threadToggle.d.ts.map +1 -0
  980. package/dist/utils/threadToggle.js +39 -0
  981. package/dist/utils/threadToggle.js.map +1 -0
  982. package/dist/utils/timezone.d.ts +125 -0
  983. package/dist/utils/timezone.d.ts.map +1 -0
  984. package/dist/utils/timezone.js +278 -0
  985. package/dist/utils/timezone.js.map +1 -0
  986. package/dist/utils/tmuxExec.d.ts +61 -0
  987. package/dist/utils/tmuxExec.d.ts.map +1 -0
  988. package/dist/utils/tmuxExec.js +109 -0
  989. package/dist/utils/tmuxExec.js.map +1 -0
  990. package/dist/utils/tmuxSessionName.d.ts +34 -0
  991. package/dist/utils/tmuxSessionName.d.ts.map +1 -0
  992. package/dist/utils/tmuxSessionName.js +51 -0
  993. package/dist/utils/tmuxSessionName.js.map +1 -0
  994. package/dist/utils/toolResultRender.d.ts +61 -0
  995. package/dist/utils/toolResultRender.d.ts.map +1 -0
  996. package/dist/utils/toolResultRender.js +62 -0
  997. package/dist/utils/toolResultRender.js.map +1 -0
  998. package/dist/utils/topicRequest.d.ts +61 -0
  999. package/dist/utils/topicRequest.d.ts.map +1 -0
  1000. package/dist/utils/topicRequest.js +68 -0
  1001. package/dist/utils/topicRequest.js.map +1 -0
  1002. package/dist/utils/topicView.d.ts +55 -0
  1003. package/dist/utils/topicView.d.ts.map +1 -0
  1004. package/dist/utils/topicView.js +56 -0
  1005. package/dist/utils/topicView.js.map +1 -0
  1006. package/dist/utils/transcribeAudio.d.ts +89 -0
  1007. package/dist/utils/transcribeAudio.d.ts.map +1 -0
  1008. package/dist/utils/transcribeAudio.js +249 -0
  1009. package/dist/utils/transcribeAudio.js.map +1 -0
  1010. package/dist/utils/transientFrames.d.ts +52 -0
  1011. package/dist/utils/transientFrames.d.ts.map +1 -0
  1012. package/dist/utils/transientFrames.js +41 -0
  1013. package/dist/utils/transientFrames.js.map +1 -0
  1014. package/dist/utils/typingActive.d.ts +35 -0
  1015. package/dist/utils/typingActive.d.ts.map +1 -0
  1016. package/dist/utils/typingActive.js +22 -0
  1017. package/dist/utils/typingActive.js.map +1 -0
  1018. package/dist/utils/typingLoopBackstop.d.ts +64 -0
  1019. package/dist/utils/typingLoopBackstop.d.ts.map +1 -0
  1020. package/dist/utils/typingLoopBackstop.js +68 -0
  1021. package/dist/utils/typingLoopBackstop.js.map +1 -0
  1022. package/dist/utils/verbosityRender.d.ts +18 -0
  1023. package/dist/utils/verbosityRender.d.ts.map +1 -0
  1024. package/dist/utils/verbosityRender.js +17 -0
  1025. package/dist/utils/verbosityRender.js.map +1 -0
  1026. package/dist/utils/wedgeRecovery.d.ts +57 -0
  1027. package/dist/utils/wedgeRecovery.d.ts.map +1 -0
  1028. package/dist/utils/wedgeRecovery.js +24 -0
  1029. package/dist/utils/wedgeRecovery.js.map +1 -0
  1030. package/dist/utils.d.ts +30 -0
  1031. package/dist/utils.d.ts.map +1 -0
  1032. package/dist/utils.js +61 -0
  1033. package/dist/utils.js.map +1 -0
  1034. package/dist/validation.d.ts +113 -0
  1035. package/dist/validation.d.ts.map +1 -0
  1036. package/dist/validation.js +226 -0
  1037. package/dist/validation.js.map +1 -0
  1038. package/dist/voiceQueue.d.ts +7 -0
  1039. package/dist/voiceQueue.d.ts.map +1 -0
  1040. package/dist/voiceQueue.js +33 -0
  1041. package/dist/voiceQueue.js.map +1 -0
  1042. package/examples/.env.example +17 -0
  1043. package/examples/docker-compose.yml +36 -0
  1044. package/package.json +83 -4
package/dist/state.js ADDED
@@ -0,0 +1,2128 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports._fsForTests = exports.StateStore = exports.KeyLock = exports.compactIdleTrackingPersistStepMs = exports.STATE_SCHEMA_VERSION = void 0;
37
+ exports.writeFileAtomic = writeFileAtomic;
38
+ exports.loadStateFile = loadStateFile;
39
+ exports.migrateLegacyMessageIdsFile = migrateLegacyMessageIdsFile;
40
+ exports.resolveDataDir = resolveDataDir;
41
+ exports.getStateStore = getStateStore;
42
+ exports._resetStateStoreSingletonForTests = _resetStateStoreSingletonForTests;
43
+ const fs = __importStar(require("fs"));
44
+ exports._fsForTests = fs;
45
+ const fs_1 = require("fs");
46
+ const path = __importStar(require("path"));
47
+ const os = __importStar(require("os"));
48
+ const node_crypto_1 = require("node:crypto");
49
+ const sessionKey_1 = require("./sessionKey");
50
+ const displayVerbosity_1 = require("./utils/displayVerbosity");
51
+ const topicView_1 = require("./utils/topicView");
52
+ const compactOnIdle_1 = require("./utils/compactOnIdle");
53
+ const autoContinueOnLimit_1 = require("./utils/autoContinueOnLimit");
54
+ const claudeProfiles_1 = require("./utils/claudeProfiles");
55
+ const modelRecentPlan_1 = require("./utils/modelRecentPlan");
56
+ const fallbackChain_1 = require("./utils/fallbackChain");
57
+ const requestGroup_1 = require("./requests/requestGroup");
58
+ /**
59
+ * @description On-disk state for the multi-thread telegram bot.
60
+ *
61
+ * Replaces the legacy `~/.telegram-bot-messages.json` (which only persisted
62
+ * `messageIds` per user) with a richer per-`SessionKey` schema:
63
+ *
64
+ * - `bindings[key]` → thread is attached to a subdir under `WORK_ROOT`
65
+ * - `agents[key]` → which adapter the thread is using, model, session ids
66
+ * - `messages[key]` → message ids tracked by `/clear`
67
+ *
68
+ * Schema source: plan §9. Field additions for D14 (`claudeSessionId`), §13.19
69
+ * (`opencodeSessionId`) and D49 (`bindings[key].closed`) are inlined here.
70
+ *
71
+ * The file is rewritten atomically (`writeFile(tmp) → fsync(fd) → rename →
72
+ * fsync(parentDir)`, plan §13.14, T4). Concurrent mutations to the same
73
+ * `SessionKey` are serialised by a per-key async lock (plan §13.15, E3).
74
+ *
75
+ * **This module is intentionally NOT yet imported from `bot.ts`.** Plan §11
76
+ * Этап 2 wires state in only on the following commit (Этап 3) — keeping the
77
+ * landing surgical.
78
+ */
79
+ /** Current schema version stamped into `state.json`. Bump on breaking layout changes. */
80
+ exports.STATE_SCHEMA_VERSION = 1;
81
+ /** Default debounce window (ms) for batched saves. Overridable via the store constructor for tests. */
82
+ const DEFAULT_SAVE_DEBOUNCE_MS = 500;
83
+ /**
84
+ * Coarse step (ms) the activity/turn stamps must move past the last PERSISTED
85
+ * value before a save is scheduled. Load-bearing for cost, not correctness:
86
+ * those two stamps are written on EVERY agent output chunk, and `state.json` is
87
+ * rewritten WHOLE (atomically, with fsync) per save — so a `scheduleSave()` per
88
+ * chunk would turn a streaming turn into a debounced disk-rewrite loop. A
89
+ * 60-second granularity is irrelevant against the 55-minute idle threshold.
90
+ */
91
+ exports.compactIdleTrackingPersistStepMs = 60_000;
92
+ /** Empty state used both for fresh installs and after a corruption-archive event. */
93
+ function emptyState() {
94
+ return {
95
+ version: exports.STATE_SCHEMA_VERSION,
96
+ bindings: {},
97
+ agents: {},
98
+ messages: {},
99
+ };
100
+ }
101
+ /** Per-thread message-id cap — older ids beyond this are dropped to keep state small. */
102
+ const MESSAGE_ID_RING_CAP = 500;
103
+ /** Byte length of the lazily-minted scheduler MCP HMAC secret (32 bytes → 64 hex chars). */
104
+ const schedulerMcpSecretByteLength = 32;
105
+ /**
106
+ * @description Promise-chain per-key lock. The map holds the tail of each
107
+ * key's queue; new callers attach to the tail and append themselves. Errors
108
+ * in one task don't poison followers — the chain only carries scheduling,
109
+ * not values. See plan §13.15 (E3).
110
+ *
111
+ * Exported for unit tests (plan §11 Этап 7, R5).
112
+ */
113
+ class KeyLock {
114
+ chains = new Map();
115
+ async withLock(keyStr, fn) {
116
+ const prev = this.chains.get(keyStr);
117
+ const run = async () => {
118
+ if (prev) {
119
+ try {
120
+ await prev;
121
+ }
122
+ catch {
123
+ // Swallow predecessor failures — they shouldn't cascade to followers.
124
+ }
125
+ }
126
+ return fn();
127
+ };
128
+ const current = run();
129
+ // Store a swallowed-error variant so this.chains.get() always resolves.
130
+ const tracked = current.then(() => undefined, () => undefined);
131
+ this.chains.set(keyStr, tracked);
132
+ try {
133
+ return await current;
134
+ }
135
+ finally {
136
+ // If no follower has overwritten our slot, drop the entry to avoid leaks.
137
+ if (this.chains.get(keyStr) === tracked) {
138
+ this.chains.delete(keyStr);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ exports.KeyLock = KeyLock;
144
+ /**
145
+ * @description Best-effort atomic write of `content` to `finalPath`.
146
+ *
147
+ * Sequence: `writeFile(tmp)` → `fsync(tmp)` → `rename(tmp, final)` →
148
+ * `fsync(parentDir)`. The last step is critical: without it, `rename`
149
+ * survives crashes but the new directory entry can be lost on a power
150
+ * cut on ext4 (plan §13.14, fix to T4).
151
+ *
152
+ * Tmp files are colocated with the final file so `rename` stays on the
153
+ * same filesystem (cross-fs rename would degrade to copy+unlink and break
154
+ * atomicity). Permissions on the tmp default to `0600` so a state file
155
+ * containing model preferences / session ids isn't world-readable on a
156
+ * shared host.
157
+ *
158
+ * Directory `fsync` is wrapped in a soft try/catch: on Windows opening a
159
+ * directory for read isn't supported and would throw. Our deployment
160
+ * target is Linux/Mac (plan §17.4 «Windows: not our case»), so logging
161
+ * and continuing is the right trade.
162
+ */
163
+ async function writeFileAtomic(finalPath, content, options = {}) {
164
+ const mode = options.mode ?? 0o600;
165
+ const dir = path.dirname(finalPath);
166
+ const tmp = path.join(dir, `.${path.basename(finalPath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
167
+ // 1. write + fsync the tmp file
168
+ const fh = await fs_1.promises.open(tmp, 'w', mode);
169
+ try {
170
+ await fh.writeFile(content);
171
+ await fh.sync();
172
+ }
173
+ finally {
174
+ await fh.close();
175
+ }
176
+ // 2. atomic rename
177
+ try {
178
+ await fs_1.promises.rename(tmp, finalPath);
179
+ }
180
+ catch (e) {
181
+ // Try to clean up the tmp if rename fails so we don't litter the dir.
182
+ await fs_1.promises.unlink(tmp).catch(() => { });
183
+ throw e;
184
+ }
185
+ // 3. fsync the parent directory so the rename hits disk
186
+ try {
187
+ const dh = await fs_1.promises.open(dir, 'r');
188
+ try {
189
+ await dh.sync();
190
+ }
191
+ finally {
192
+ await dh.close();
193
+ }
194
+ }
195
+ catch (e) {
196
+ // EISDIR / ENOTSUP on Windows, AIX etc. We document Linux/Mac so this is benign.
197
+ const code = e.code;
198
+ if (code !== 'EISDIR' && code !== 'EPERM' && code !== 'ENOTSUP' && code !== 'EINVAL') {
199
+ console.warn(`[state] parent dir fsync failed (${code}):`, e);
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * @description Try to load the state file. On a successful parse return the
205
+ * structured state. On parse failure archive the broken file to
206
+ * `state.json.corrupted-<iso>` and return `null` so the caller can start fresh.
207
+ *
208
+ * Three cases the caller cares about:
209
+ *
210
+ * 1. File doesn't exist → `null`, no archive. Fresh install.
211
+ * 2. File exists, parses → state object.
212
+ * 3. File exists, parses fails → archived, `null` returned.
213
+ * We also handle EPERM / EACCES on read here for completeness — same
214
+ * treatment: archive (if possible) and start fresh.
215
+ *
216
+ * Exported for unit tests (plan §11 Этап 7, R6).
217
+ */
218
+ async function loadStateFile(filePath) {
219
+ let raw;
220
+ try {
221
+ raw = await fs_1.promises.readFile(filePath, 'utf-8');
222
+ }
223
+ catch (e) {
224
+ const code = e.code;
225
+ if (code === 'ENOENT')
226
+ return { ok: false, reason: 'missing' };
227
+ // Unreadable (perms, disk error) — treat like corruption so the bot can boot.
228
+ return {
229
+ ok: false,
230
+ reason: 'corrupted',
231
+ archivedTo: null,
232
+ error: `read failed (${code}): ${e.message}`,
233
+ };
234
+ }
235
+ try {
236
+ const parsed = JSON.parse(raw);
237
+ // Light shape validation — full migrations would land here on a version bump.
238
+ if (typeof parsed === 'object' && parsed !== null &&
239
+ typeof parsed.version === 'number' &&
240
+ parsed.bindings && typeof parsed.bindings === 'object' &&
241
+ parsed.agents && typeof parsed.agents === 'object' &&
242
+ parsed.messages && typeof parsed.messages === 'object') {
243
+ return { ok: true, state: parsed };
244
+ }
245
+ throw new Error('shape mismatch');
246
+ }
247
+ catch (e) {
248
+ const archivedTo = await archiveCorruptedFile(filePath);
249
+ return {
250
+ ok: false,
251
+ reason: 'corrupted',
252
+ archivedTo,
253
+ error: e.message,
254
+ };
255
+ }
256
+ }
257
+ /**
258
+ * @description Rename a corrupted state file out of the way so the bot
259
+ * can boot with a fresh state without losing forensic evidence.
260
+ *
261
+ * Returns the archive path on success, `null` if archival itself failed
262
+ * (in which case we'll log and the caller proceeds with fresh state anyway).
263
+ */
264
+ async function archiveCorruptedFile(filePath) {
265
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
266
+ const archive = `${filePath}.corrupted-${ts}`;
267
+ try {
268
+ await fs_1.promises.rename(filePath, archive);
269
+ return archive;
270
+ }
271
+ catch (e) {
272
+ console.error(`[state] failed to archive corrupted file ${filePath}:`, e);
273
+ return null;
274
+ }
275
+ }
276
+ /**
277
+ * @description Migrate the legacy single-folder bot's message-id file.
278
+ *
279
+ * Old layout: `~/.telegram-bot-messages.json` keyed by raw user id.
280
+ * The 2.0 release intentionally doesn't try to merge those ids into the
281
+ * new `SessionKey`-keyed schema — the routing model changed too much for
282
+ * a clean mapping (plan §9). We just rename the file so it's preserved
283
+ * for the user's records and so it stops being read on every boot.
284
+ *
285
+ * Returns the archive path if migration happened, `null` otherwise.
286
+ */
287
+ async function migrateLegacyMessageIdsFile() {
288
+ const legacy = path.join(os.homedir(), '.telegram-bot-messages.json');
289
+ try {
290
+ await fs_1.promises.access(legacy);
291
+ }
292
+ catch {
293
+ return null;
294
+ }
295
+ const backup = `${legacy}.bak`;
296
+ try {
297
+ await fs_1.promises.rename(legacy, backup);
298
+ return backup;
299
+ }
300
+ catch (e) {
301
+ console.warn(`[state] failed to archive legacy ${legacy}:`, e);
302
+ return null;
303
+ }
304
+ }
305
+ /**
306
+ * @description Stateful, async-safe store for `state.json`.
307
+ *
308
+ * Construction is cheap (no I/O). Call `init()` once at boot before any
309
+ * setters fire; init loads or initialises the file, performs the legacy
310
+ * migration, and resolves. Setters return promises so callers can await
311
+ * the resulting persisted state when correctness matters more than
312
+ * throughput; non-critical writes are debounced.
313
+ */
314
+ class StateStore {
315
+ dataDir;
316
+ statePath;
317
+ saveDebounceMs;
318
+ state = emptyState();
319
+ /** True if the previous on-disk state file was unreadable / corrupt. */
320
+ corruptedOnLoad = false;
321
+ /** Archive path if the previous on-disk state was corrupted. */
322
+ corruptedArchivePath = null;
323
+ /** Backup path if the legacy file was migrated. */
324
+ legacyMigrationPath = null;
325
+ saveTimer = null;
326
+ /** Tail of the chained writes so two flushes don't try to rename simultaneously. */
327
+ writeChain = Promise.resolve();
328
+ /**
329
+ * Last compact-on-idle activity/turn stamp each thread actually asked to
330
+ * PERSIST, used to apply {@link compactIdleTrackingPersistStepMs}. In-memory
331
+ * only and deliberately NOT written to disk — it is bookkeeping ABOUT the
332
+ * bookkeeping, and after a restart the first stamp simply schedules one save.
333
+ */
334
+ compactIdleTrackingSavedAt = new Map();
335
+ keyLock = new KeyLock();
336
+ constructor(dataDir, options = {}) {
337
+ this.dataDir = dataDir;
338
+ this.statePath = path.join(dataDir, 'state.json');
339
+ this.saveDebounceMs = options.saveDebounceMs ?? DEFAULT_SAVE_DEBOUNCE_MS;
340
+ }
341
+ /**
342
+ * @description One-shot boot. Creates `dataDir` if missing, performs
343
+ * legacy migration, loads the existing state file (or starts fresh on
344
+ * corruption).
345
+ */
346
+ async init() {
347
+ await fs_1.promises.mkdir(this.dataDir, { recursive: true, mode: 0o700 });
348
+ // `mkdir`'s mode applies only when the dir is CREATED — an existing deploy
349
+ // (or a dir created earlier in this boot by the console tap / trace writer)
350
+ // keeps its old bits. Heal to owner-only: DATA_DIR holds state.json, traces
351
+ // and console logs that quote prompts/session ids. Best-effort — an exotic
352
+ // filesystem may refuse chmod, which must not block boot.
353
+ try {
354
+ await fs_1.promises.chmod(this.dataDir, 0o700);
355
+ }
356
+ catch (e) {
357
+ console.warn(`[state] could not chmod DATA_DIR to owner-only:`, e instanceof Error ? e.message : e);
358
+ }
359
+ this.legacyMigrationPath = await migrateLegacyMessageIdsFile();
360
+ const loaded = await loadStateFile(this.statePath);
361
+ if (loaded.ok) {
362
+ this.state = loaded.state;
363
+ // Top up missing fields if loaded from an older v1 file.
364
+ this.state.bindings ??= {};
365
+ this.state.agents ??= {};
366
+ this.state.messages ??= {};
367
+ }
368
+ else if (loaded.reason === 'missing') {
369
+ this.state = emptyState();
370
+ // No save: nothing yet to persist. First setter will trigger a debounce.
371
+ }
372
+ else {
373
+ this.corruptedOnLoad = true;
374
+ this.corruptedArchivePath = loaded.archivedTo;
375
+ console.error(`[state] previous state.json was corrupted (${loaded.error}); ` +
376
+ `archived to ${loaded.archivedTo ?? '(archive failed)'}, starting fresh`);
377
+ this.state = emptyState();
378
+ await this.flush();
379
+ }
380
+ }
381
+ /** Path of the live state file (for logging / docs / smoke-tests). */
382
+ get stateFilePath() {
383
+ return this.statePath;
384
+ }
385
+ /** True iff the previous boot's state file was unreadable on this start. */
386
+ wasCorruptedOnLoad() {
387
+ return this.corruptedOnLoad;
388
+ }
389
+ /** Where the previous corrupted file was moved, if archival succeeded. */
390
+ getCorruptedArchivePath() {
391
+ return this.corruptedArchivePath;
392
+ }
393
+ /** Where the legacy `~/.telegram-bot-messages.json` was archived, if it existed. */
394
+ getLegacyMigrationPath() {
395
+ return this.legacyMigrationPath;
396
+ }
397
+ // ── locking ──
398
+ /**
399
+ * @description Run `fn` while holding the per-key lock for `key`. Concurrent
400
+ * calls for the SAME key are serialised; calls for different keys run in
401
+ * parallel. Failure in one holder does NOT propagate to followers (the lock
402
+ * is for serialisation only; error handling is each caller's job).
403
+ */
404
+ withLock(key, fn) {
405
+ return this.keyLock.withLock((0, sessionKey_1.keyToString)(key), fn);
406
+ }
407
+ // ── bindings ──
408
+ getBinding(key) {
409
+ return this.state.bindings[(0, sessionKey_1.keyToString)(key)] ?? null;
410
+ }
411
+ async setBinding(key, subdir, options = {}) {
412
+ const k = (0, sessionKey_1.keyToString)(key);
413
+ await this.withLock(key, async () => {
414
+ const existing = this.state.bindings[k];
415
+ // Audit S20 / #44: previous code path used `existing?.closed ? …`,
416
+ // which falsy-collapsed an explicit `closed: false` and dropped
417
+ // the field entirely. Now we carry `closed` through verbatim,
418
+ // letting `false` and `true` round-trip independently.
419
+ const next = {
420
+ subdir,
421
+ createdAt: existing?.createdAt ?? new Date().toISOString(),
422
+ };
423
+ if (options.closed !== undefined) {
424
+ next.closed = options.closed;
425
+ }
426
+ else if (existing?.closed !== undefined) {
427
+ next.closed = existing.closed;
428
+ }
429
+ if (existing?.pinnedStatusMessageId !== undefined) {
430
+ next.pinnedStatusMessageId = existing.pinnedStatusMessageId;
431
+ }
432
+ // Carry the known topic name through a re-bind. A caller that learned
433
+ // the name (e.g. `/new`, deferred pending-name copy) passes it
434
+ // explicitly; otherwise we keep whatever was already stored.
435
+ if (options.topicName !== undefined) {
436
+ next.topicName = options.topicName;
437
+ }
438
+ else if (existing?.topicName !== undefined) {
439
+ next.topicName = existing.topicName;
440
+ }
441
+ this.state.bindings[k] = next;
442
+ this.scheduleSave();
443
+ });
444
+ }
445
+ /**
446
+ * @description Persist the forum-topic display name for a thread. Used by
447
+ * `forum_topic_edited` (rename) when a binding already exists. No-op (no
448
+ * save) when the binding is absent or the name is unchanged — symmetric to
449
+ * {@link setBindingPinnedStatusText}.
450
+ */
451
+ async setBindingTopicName(key, topicName) {
452
+ const k = (0, sessionKey_1.keyToString)(key);
453
+ await this.withLock(key, async () => {
454
+ const existing = this.state.bindings[k];
455
+ if (!existing)
456
+ return;
457
+ if (existing.topicName === topicName)
458
+ return;
459
+ this.state.bindings[k] = { ...existing, topicName };
460
+ this.scheduleSave();
461
+ });
462
+ }
463
+ async setBindingClosed(key, closed) {
464
+ const k = (0, sessionKey_1.keyToString)(key);
465
+ await this.withLock(key, async () => {
466
+ const existing = this.state.bindings[k];
467
+ if (!existing)
468
+ return;
469
+ this.state.bindings[k] = { ...existing, closed };
470
+ this.scheduleSave();
471
+ });
472
+ }
473
+ /**
474
+ * @description Persist the pinned status banner's message id for a thread,
475
+ * or clear it (pass `null`). Skipped if the binding doesn't exist — we
476
+ * don't ever want a pinned-message row dangling without a parent binding.
477
+ *
478
+ * Caller is expected to actually pin / unpin in Telegram around this
479
+ * call; this method only updates the on-disk pointer. Plan §20.5.
480
+ */
481
+ async setBindingPinnedStatusMessageId(key, messageId) {
482
+ const k = (0, sessionKey_1.keyToString)(key);
483
+ await this.withLock(key, async () => {
484
+ const existing = this.state.bindings[k];
485
+ if (!existing)
486
+ return;
487
+ if (messageId === null) {
488
+ if (existing.pinnedStatusMessageId === undefined)
489
+ return;
490
+ const { pinnedStatusMessageId: _drop, ...rest } = existing;
491
+ this.state.bindings[k] = rest;
492
+ }
493
+ else {
494
+ if (existing.pinnedStatusMessageId === messageId)
495
+ return;
496
+ this.state.bindings[k] = { ...existing, pinnedStatusMessageId: messageId };
497
+ }
498
+ this.scheduleSave();
499
+ });
500
+ }
501
+ /**
502
+ * @description Persist the last banner text sent/edited for a thread, or
503
+ * clear it (pass `null`). Mirrors {@link setBindingPinnedStatusMessageId}:
504
+ * skipped if the binding doesn't exist, and a no-op (no save) when the value
505
+ * is unchanged. Read back at boot to seed `pinnedStatusTextCache` so the
506
+ * startup refresh wave skips identical-banner edits (B8).
507
+ */
508
+ async setBindingPinnedStatusText(key, text) {
509
+ const k = (0, sessionKey_1.keyToString)(key);
510
+ await this.withLock(key, async () => {
511
+ const existing = this.state.bindings[k];
512
+ if (!existing)
513
+ return;
514
+ if (text === null) {
515
+ if (existing.pinnedStatusText === undefined)
516
+ return;
517
+ const { pinnedStatusText: _drop, ...rest } = existing;
518
+ this.state.bindings[k] = rest;
519
+ }
520
+ else {
521
+ if (existing.pinnedStatusText === text)
522
+ return;
523
+ this.state.bindings[k] = { ...existing, pinnedStatusText: text };
524
+ }
525
+ this.scheduleSave();
526
+ });
527
+ }
528
+ async removeBinding(key) {
529
+ const k = (0, sessionKey_1.keyToString)(key);
530
+ await this.withLock(key, async () => {
531
+ delete this.state.bindings[k];
532
+ delete this.state.agents[k];
533
+ delete this.state.messages[k];
534
+ // The thread is gone (unbind / deleted topic) → so is anything a boot
535
+ // recovery could resume for it, and the record of its pinned answer.
536
+ this.dropLimitEpisodeRecovered(k);
537
+ this.dropOptionalMapEntry('answerPins', k);
538
+ this.scheduleSave();
539
+ });
540
+ }
541
+ listBindings() {
542
+ const out = [];
543
+ for (const [keyStr, data] of Object.entries(this.state.bindings)) {
544
+ const key = (0, sessionKey_1.tryKeyFromString)(keyStr);
545
+ if (key)
546
+ out.push({ key, data });
547
+ }
548
+ return out;
549
+ }
550
+ /**
551
+ * @description Find every thread currently bound to a given subdir.
552
+ * Plan §10.6 names this for `/bind` UX warning «📁 уже работают треды: ...»
553
+ * — one folder may be reached from several threads (D7).
554
+ */
555
+ listKeysForSubdir(subdir) {
556
+ const out = [];
557
+ for (const [keyStr, data] of Object.entries(this.state.bindings)) {
558
+ if (data.subdir === subdir) {
559
+ const key = (0, sessionKey_1.tryKeyFromString)(keyStr);
560
+ if (key)
561
+ out.push(key);
562
+ }
563
+ }
564
+ return out;
565
+ }
566
+ // ── agents ──
567
+ getAgent(key) {
568
+ return this.state.agents[(0, sessionKey_1.keyToString)(key)] ?? null;
569
+ }
570
+ /**
571
+ * @description Upsert agent state for a thread. `name` is required (we
572
+ * don't keep an agent record without a backend choice); the rest of the
573
+ * fields are patched onto whatever exists.
574
+ */
575
+ async setAgent(key, data) {
576
+ const k = (0, sessionKey_1.keyToString)(key);
577
+ await this.withLock(key, async () => {
578
+ const existing = this.state.agents[k];
579
+ this.state.agents[k] = { ...existing, ...data };
580
+ this.scheduleSave();
581
+ });
582
+ }
583
+ async removeAgent(key) {
584
+ const k = (0, sessionKey_1.keyToString)(key);
585
+ await this.withLock(key, async () => {
586
+ delete this.state.agents[k];
587
+ this.scheduleSave();
588
+ });
589
+ }
590
+ getClaudeSessionId(key) {
591
+ return this.state.agents[(0, sessionKey_1.keyToString)(key)]?.claudeSessionId ?? null;
592
+ }
593
+ /**
594
+ * @description Persist the Claude `--session-id` UUID for `key`.
595
+ *
596
+ * If the thread already has an agent record, only the `claudeSessionId`
597
+ * field is patched — we keep whatever `name` was previously chosen so a
598
+ * thread currently set to `opencode` doesn't get silently flipped to
599
+ * `claude` just because we recorded a UUID. The bot picks the adapter
600
+ * via {@link setAgent} or `setThreadAdapter` — those are the right
601
+ * places to change `name`.
602
+ */
603
+ async setClaudeSessionId(key, uuid) {
604
+ const k = (0, sessionKey_1.keyToString)(key);
605
+ await this.withLock(key, async () => {
606
+ const existing = this.state.agents[k];
607
+ this.state.agents[k] = existing
608
+ ? { ...existing, claudeSessionId: uuid }
609
+ : { name: 'claude', claudeSessionId: uuid };
610
+ this.scheduleSave();
611
+ });
612
+ }
613
+ getOpenCodeSessionId(key) {
614
+ return this.state.agents[(0, sessionKey_1.keyToString)(key)]?.opencodeSessionId ?? null;
615
+ }
616
+ /** Symmetric to {@link setClaudeSessionId} — does not flip `agent.name`. */
617
+ async setOpenCodeSessionId(key, id) {
618
+ const k = (0, sessionKey_1.keyToString)(key);
619
+ await this.withLock(key, async () => {
620
+ const existing = this.state.agents[k];
621
+ this.state.agents[k] = existing
622
+ ? { ...existing, opencodeSessionId: id }
623
+ : { name: 'opencode', opencodeSessionId: id };
624
+ this.scheduleSave();
625
+ });
626
+ }
627
+ /**
628
+ * @description Advance the per-thread {@link SeenWatermark} (set at each turn
629
+ * end by the adapters via the injected writer). Merges onto the EXISTING agent
630
+ * row only — never flips `agent.name` (symmetric to
631
+ * {@link setClaudeSessionId} / {@link setOpenCodeSessionId}) and, unlike them,
632
+ * never CREATES a row: a watermark is meaningless without a live agent session
633
+ * (which always recorded its row first), so a write for an unknown thread is a
634
+ * no-op, not a dangling agent row.
635
+ */
636
+ async setSeenWatermark(key, watermark) {
637
+ const k = (0, sessionKey_1.keyToString)(key);
638
+ await this.withLock(key, async () => {
639
+ const existing = this.state.agents[k];
640
+ if (!existing)
641
+ return;
642
+ this.state.agents[k] = { ...existing, seenWatermark: watermark };
643
+ this.scheduleSave();
644
+ });
645
+ }
646
+ /**
647
+ * @description Persist the json-stream stdout tail offset (advanced by the
648
+ * adapter's tail poller through the injected writer, sealed by the shutdown
649
+ * `flush()`). Merge-only like {@link setSeenWatermark}: never flips
650
+ * `agent.name` and never creates a row — an offset is meaningless without
651
+ * the agent row its session recorded first.
652
+ */
653
+ async setJsonStreamTail(key, tail) {
654
+ const k = (0, sessionKey_1.keyToString)(key);
655
+ await this.withLock(key, async () => {
656
+ const existing = this.state.agents[k];
657
+ if (!existing)
658
+ return;
659
+ this.state.agents[k] = { ...existing, jsonStreamTail: tail };
660
+ this.scheduleSave();
661
+ });
662
+ }
663
+ /**
664
+ * @description Persist the session-start timestamp (a local-offset ISO string;
665
+ * see {@link AgentData.startedAt}). Merge-only like {@link setSeenWatermark}:
666
+ * never flips `agent.name` and never CREATES a row — the caller records the
667
+ * agent row (via `setAgent` / `persistAdapterSessionIds`) first, so a write
668
+ * for an unknown thread is a no-op rather than a dangling agent row.
669
+ */
670
+ async setAgentStartedAt(key, startedAt) {
671
+ const k = (0, sessionKey_1.keyToString)(key);
672
+ await this.withLock(key, async () => {
673
+ const existing = this.state.agents[k];
674
+ if (!existing)
675
+ return;
676
+ this.state.agents[k] = { ...existing, startedAt };
677
+ this.scheduleSave();
678
+ });
679
+ }
680
+ /** Persist the tool digest a json-stream process was started with (see {@link AgentData.mcpToolDigest}); merge-only. */
681
+ async setAgentMcpToolDigest(key, mcpToolDigest) {
682
+ const k = (0, sessionKey_1.keyToString)(key);
683
+ await this.withLock(key, async () => {
684
+ const existing = this.state.agents[k];
685
+ if (!existing)
686
+ return;
687
+ this.state.agents[k] = { ...existing, mcpToolDigest };
688
+ this.scheduleSave();
689
+ });
690
+ }
691
+ /**
692
+ * @description Release both persisted session ids (and the json-stream tail
693
+ * offset, which is meaningless without its session; plus the session-start
694
+ * timestamp, which belongs to the session going away) for `key` while keeping
695
+ * `name` / `model`. Called on explicit user stop (`/quit`, `/quit-all`,
696
+ * `/new`, `/unbind`) so a later bot restart won't auto-reattach a session
697
+ * the user deliberately ended. No-op when the thread has no agent record.
698
+ */
699
+ async clearAgentSessionIds(key) {
700
+ const k = (0, sessionKey_1.keyToString)(key);
701
+ await this.withLock(key, async () => {
702
+ // A released session can never be the one a boot recovery resumes (it checks
703
+ // `checkIsActive` + the persisted session id), so its handled-limit-episode
704
+ // marker becomes a dead key — drop it with the ids instead of letting
705
+ // `state.json` accumulate one per retired session.
706
+ const droppedMarker = this.dropLimitEpisodeRecovered(k);
707
+ const existing = this.state.agents[k];
708
+ if (!existing) {
709
+ if (droppedMarker)
710
+ this.scheduleSave();
711
+ return;
712
+ }
713
+ const { claudeSessionId, opencodeSessionId, jsonStreamTail, startedAt, mcpToolDigest, ...rest } = existing;
714
+ this.state.agents[k] = rest;
715
+ // A released conversation is gone for this thread: the other family's parked id goes with it.
716
+ this.dropParkedSession(k);
717
+ this.scheduleSave();
718
+ });
719
+ }
720
+ // ── parked session ids (a thread's other backend family, `utils/fallbackChain.ts`) ──
721
+ getParkedSession(key) {
722
+ return this.state.parkedSessions?.[(0, sessionKey_1.keyToString)(key)] ?? null;
723
+ }
724
+ /** Park the id of the family the thread leaves (`null` clears). Debounced. */
725
+ async setParkedSession(key, parked) {
726
+ const k = (0, sessionKey_1.keyToString)(key);
727
+ await this.withLock(key, async () => {
728
+ if (parked === null)
729
+ this.dropParkedSession(k);
730
+ else
731
+ (this.state.parkedSessions ??= {})[k] = parked;
732
+ this.scheduleSave();
733
+ });
734
+ }
735
+ dropParkedSession(k) {
736
+ if (!this.state.parkedSessions)
737
+ return;
738
+ delete this.state.parkedSessions[k];
739
+ if (Object.keys(this.state.parkedSessions).length === 0)
740
+ delete this.state.parkedSessions;
741
+ }
742
+ // ── messages (`/clear` support) ──
743
+ getMessageIds(key) {
744
+ return this.state.messages[(0, sessionKey_1.keyToString)(key)]?.slice() ?? [];
745
+ }
746
+ /** Append IDs to the bounded in-memory ring while the caller holds the key lock. */
747
+ appendMessageIds(keyStr, messageIds) {
748
+ const list = (this.state.messages[keyStr] ??= []);
749
+ list.push(...messageIds);
750
+ if (list.length > MESSAGE_ID_RING_CAP) {
751
+ this.state.messages[keyStr] = list.slice(-MESSAGE_ID_RING_CAP);
752
+ }
753
+ }
754
+ /**
755
+ * @description Append a message id to the per-thread tracking list and
756
+ * trim to the most recent `MESSAGE_ID_RING_CAP`. Anything older falls off
757
+ * the end — Telegram won't let `deleteMessages` touch messages older than
758
+ * 48h anyway (plan §11 Этап 3, U2).
759
+ */
760
+ async pushMessageId(key, msgId) {
761
+ const k = (0, sessionKey_1.keyToString)(key);
762
+ await this.withLock(key, async () => {
763
+ this.appendMessageIds(k, [msgId]);
764
+ this.scheduleSave();
765
+ });
766
+ }
767
+ /** Atomically append and durably persist one Telegram response's message IDs. */
768
+ async pushMessageIds(key, messageIds) {
769
+ if (messageIds.length === 0)
770
+ return;
771
+ const k = (0, sessionKey_1.keyToString)(key);
772
+ await this.withLock(key, async () => {
773
+ this.appendMessageIds(k, messageIds);
774
+ await this.flush();
775
+ });
776
+ }
777
+ /** Atomically hand tracked IDs to `/clear_messages` while later pushes remain tracked. */
778
+ async takeMessageIds(key, additionalIds = []) {
779
+ const k = (0, sessionKey_1.keyToString)(key);
780
+ return this.withLock(key, async () => {
781
+ const messageIds = [...(this.state.messages[k] ?? []), ...additionalIds];
782
+ if (this.state.messages[k] !== undefined) {
783
+ delete this.state.messages[k];
784
+ this.scheduleSave();
785
+ }
786
+ return messageIds;
787
+ });
788
+ }
789
+ // ── heartbeat (hot-reload vs cold-start detection) ──
790
+ /**
791
+ * @description Stamp `lastHeartbeatAt = now` (ms) and schedule a debounced
792
+ * save. Called by the bot on a ~10s interval while running so the next
793
+ * boot can tell a hot reload (gap < threshold) from a real cold start
794
+ * (gap large, or no stamp at all on a fresh install).
795
+ *
796
+ * Cheap by design: a one-field mutation that piggy-backs on the existing
797
+ * debounced save loop. We don't `flush()` here — losing the last 10s
798
+ * heartbeat on a hard crash just biases the next boot toward
799
+ * "cold start", which is the conservative direction.
800
+ */
801
+ touchHeartbeat(now = Date.now()) {
802
+ this.state.lastHeartbeatAt = now;
803
+ this.scheduleSave();
804
+ }
805
+ /**
806
+ * @description Time since the last persisted heartbeat in ms, or `null`
807
+ * if the state file has no heartbeat stamp (fresh install, or pre-feature
808
+ * state file). `null` should be treated as "unknown → assume cold start".
809
+ *
810
+ * Read at boot to decide hot-vs-cold-start, ideally BEFORE the first
811
+ * `touchHeartbeat()` call or `state.flush()` (otherwise the value
812
+ * compares against ourselves and always looks like a hot reload).
813
+ */
814
+ getDowntimeMs(now = Date.now()) {
815
+ const last = this.state.lastHeartbeatAt;
816
+ if (typeof last !== 'number' || !Number.isFinite(last))
817
+ return null;
818
+ return Math.max(0, now - last);
819
+ }
820
+ // ── paired group id (auto-pairing) ──
821
+ /** The auto-paired forum supergroup id, or `null` if never paired. */
822
+ getPairedGroupId() {
823
+ return this.state.pairedGroupId ?? null;
824
+ }
825
+ /**
826
+ * @description Persist the auto-paired group id and flush immediately.
827
+ *
828
+ * Unlike per-thread mutations this is a rare, one-shot critical write:
829
+ * if the process dies right after pairing we must not lose the id and
830
+ * fall back into pairing mode on the next boot. So we `flush()` rather
831
+ * than debounce.
832
+ */
833
+ async setPairedGroupId(groupId) {
834
+ this.state.pairedGroupId = groupId;
835
+ await this.flush();
836
+ }
837
+ // ── output-trace toggle (`/trace`) ──
838
+ /**
839
+ * @description Current persisted trace toggle. `threadKeys` are the
840
+ * {@link SessionKey} strings opted into tracing; `allThreads` traces
841
+ * everything. `allThreads` defaults to TRUE on a fresh or pre-feature state
842
+ * file (always-on observability — only an explicit `/trace off all` turns it
843
+ * off, stored as a durable `false`); `threadKeys` defaults empty. Read at boot
844
+ * to seed `outputTrace.ts`.
845
+ */
846
+ getTraceConfig() {
847
+ return {
848
+ allThreads: this.state.traceAllThreads ?? true,
849
+ threadKeys: this.state.tracedThreads?.slice() ?? [],
850
+ };
851
+ }
852
+ /**
853
+ * @description Persist the trace toggle. Not crash-critical (a debug aid), so
854
+ * it rides the debounced save loop rather than an immediate `flush()`. The
855
+ * `tracedThreads` array is normalised to a deduped, sorted form so the
856
+ * on-disk shape is stable across toggles; an empty list is dropped.
857
+ *
858
+ * `traceAllThreads` is stored EXPLICITLY as a boolean (including `false`):
859
+ * the all-threads default is now ON ({@link getTraceConfig}), so a `/trace
860
+ * off all` must be DURABLE and distinguishable from "never set" (which reads
861
+ * back as the ON default). Dropping it on `false` would silently re-enable
862
+ * tracing on the next boot.
863
+ */
864
+ async setTraceConfig(config) {
865
+ const uniqueKeys = [...new Set(config.threadKeys)].sort();
866
+ if (uniqueKeys.length > 0)
867
+ this.state.tracedThreads = uniqueKeys;
868
+ else
869
+ delete this.state.tracedThreads;
870
+ this.state.traceAllThreads = config.allThreads;
871
+ this.scheduleSave();
872
+ }
873
+ // ── per-chat locale (`/language`) ──
874
+ getChatLocaleOverride(chatId) {
875
+ return this.state.chatLocaleOverrides?.[chatId.toString()] ?? null;
876
+ }
877
+ getChatTelegramLocale(chatId) {
878
+ return this.state.chatTelegramLocales?.[chatId.toString()] ?? null;
879
+ }
880
+ async setChatLocaleOverride(chatId, locale) {
881
+ const chatIdKey = chatId.toString();
882
+ if (locale === null) {
883
+ if (!this.state.chatLocaleOverrides?.[chatIdKey])
884
+ return;
885
+ delete this.state.chatLocaleOverrides[chatIdKey];
886
+ if (Object.keys(this.state.chatLocaleOverrides).length === 0) {
887
+ delete this.state.chatLocaleOverrides;
888
+ }
889
+ this.scheduleSave();
890
+ return;
891
+ }
892
+ if (this.state.chatLocaleOverrides?.[chatIdKey] === locale)
893
+ return;
894
+ (this.state.chatLocaleOverrides ??= {})[chatIdKey] = locale;
895
+ this.scheduleSave();
896
+ }
897
+ async setChatTelegramLocale(chatId, locale) {
898
+ const chatIdKey = chatId.toString();
899
+ if (this.state.chatTelegramLocales?.[chatIdKey] === locale)
900
+ return;
901
+ (this.state.chatTelegramLocales ??= {})[chatIdKey] = locale;
902
+ this.scheduleSave();
903
+ }
904
+ // ── per-thread prompt-timestamp toggle (`/timestamps`) ──
905
+ /**
906
+ * @description Whether `/timestamps` is ON for `key` (default OFF). When ON,
907
+ * `forwardPromptToAgent` prepends the send-time as a local-offset ISO top
908
+ * line to every forwarded prompt.
909
+ */
910
+ checkIsTimestampsEnabled(key) {
911
+ return this.state.timestampThreads?.includes((0, sessionKey_1.keyToString)(key)) ?? false;
912
+ }
913
+ /**
914
+ * @description Persist the per-thread `/timestamps` toggle. Mirrors
915
+ * {@link setTraceConfig}'s shape discipline: the list is deduped + sorted so
916
+ * the on-disk form is stable, and an empty list is dropped (an all-defaults
917
+ * install leaves no trace in `state.json`). Not crash-critical, so it rides
918
+ * the debounced save loop.
919
+ */
920
+ async setTimestampsEnabled(key, isEnabled) {
921
+ const keyStr = (0, sessionKey_1.keyToString)(key);
922
+ const current = new Set(this.state.timestampThreads ?? []);
923
+ if (isEnabled)
924
+ current.add(keyStr);
925
+ else
926
+ current.delete(keyStr);
927
+ const uniqueKeys = [...current].sort();
928
+ if (uniqueKeys.length > 0)
929
+ this.state.timestampThreads = uniqueKeys;
930
+ else
931
+ delete this.state.timestampThreads;
932
+ this.scheduleSave();
933
+ }
934
+ // ── hidden `/model` providers (global, not per-thread) ──
935
+ /**
936
+ * @description Providers currently hidden from the `/model` picker, in the
937
+ * stable sorted on-disk order. Empty when nothing is hidden (the default).
938
+ */
939
+ getHiddenModelProviders() {
940
+ return this.state.hiddenModelProviders?.slice() ?? [];
941
+ }
942
+ /**
943
+ * @description Hide/show one provider in the `/model` picker. Mirrors
944
+ * {@link setTimestampsEnabled}'s shape discipline: deduped + sorted so the
945
+ * on-disk form is stable across toggles, and an empty list is dropped (a
946
+ * default install leaves no trace in `state.json`). A picker preference is
947
+ * not crash-critical, so it rides the debounced save loop.
948
+ */
949
+ async setModelProviderHidden(provider, isHidden) {
950
+ const current = new Set(this.state.hiddenModelProviders ?? []);
951
+ if (isHidden)
952
+ current.add(provider);
953
+ else
954
+ current.delete(provider);
955
+ const uniqueProviders = [...current].sort();
956
+ if (uniqueProviders.length > 0)
957
+ this.state.hiddenModelProviders = uniqueProviders;
958
+ else
959
+ delete this.state.hiddenModelProviders;
960
+ this.scheduleSave();
961
+ }
962
+ // ── recently used `/model` picks (global, not per-thread) ──
963
+ /**
964
+ * @description The recently used `provider/model` ids, most recent first.
965
+ * Empty on a fresh install. Unfiltered — the picker drops the entries the
966
+ * live catalog no longer serves (`getRecentModelsInCatalog`).
967
+ */
968
+ getRecentModels() {
969
+ return this.state.recentModels?.slice() ?? [];
970
+ }
971
+ /**
972
+ * @description Record a successful model pick: `modelId` moves to the head of
973
+ * the list (deduplicated, capped by {@link pushRecentModel}). Same shape
974
+ * discipline as {@link setModelProviderHidden} — an empty list is dropped —
975
+ * and, like it, a picker preference rides the debounced save loop.
976
+ */
977
+ async addRecentModel(modelId) {
978
+ const next = (0, modelRecentPlan_1.pushRecentModel)(this.state.recentModels ?? [], modelId);
979
+ if (next.length > 0)
980
+ this.state.recentModels = next;
981
+ else
982
+ delete this.state.recentModels;
983
+ this.scheduleSave();
984
+ }
985
+ // ── compact-on-idle toggle (`/compact_on_idle`) ──
986
+ /**
987
+ * @description The instance-wide compact-on-idle default (driven from the
988
+ * General topic). ON when unset — same always-on default as
989
+ * {@link getTraceConfig}'s `allThreads`.
990
+ */
991
+ getCompactOnIdleGlobalDefault() {
992
+ return this.state.compactOnIdleEnabled ?? true;
993
+ }
994
+ /**
995
+ * @description The per-thread compact-on-idle override, or `undefined` when the
996
+ * thread follows the instance default. A present value (true OR false) wins.
997
+ */
998
+ getCompactOnIdleOverride(key) {
999
+ return this.state.compactOnIdleOverrides?.[(0, sessionKey_1.keyToString)(key)];
1000
+ }
1001
+ /**
1002
+ * @description Whether compact-on-idle is effectively enabled for `key`: the
1003
+ * per-thread override if set, else the instance default (OFF when unset).
1004
+ */
1005
+ checkIsCompactOnIdleEnabled(key) {
1006
+ return (0, compactOnIdle_1.resolveCompactOnIdleEnabled)(this.state.compactOnIdleEnabled, this.getCompactOnIdleOverride(key));
1007
+ }
1008
+ /**
1009
+ * @description Set the instance-wide compact-on-idle default (from General).
1010
+ * Stored EXPLICITLY as a boolean (incl. `false`) so a «Disable» is durable and
1011
+ * distinguishable from "never set" (which reads back as the ON default) —
1012
+ * mirrors {@link setTraceConfig}'s `traceAllThreads` discipline. Debounced (a
1013
+ * preference, not crash-critical).
1014
+ */
1015
+ async setCompactOnIdleGlobalDefault(enabled) {
1016
+ if (this.state.compactOnIdleEnabled === enabled)
1017
+ return;
1018
+ this.state.compactOnIdleEnabled = enabled;
1019
+ this.scheduleSave();
1020
+ }
1021
+ /**
1022
+ * @description Set the per-thread compact-on-idle override (from a regular
1023
+ * topic). Stored explicitly (a present true/false wins over the global
1024
+ * default); the whole map is dropped once empty so an all-default instance
1025
+ * leaves a clean `state.json`. Debounced.
1026
+ */
1027
+ async setCompactOnIdleOverride(key, enabled) {
1028
+ const k = (0, sessionKey_1.keyToString)(key);
1029
+ await this.withLock(key, async () => {
1030
+ if (this.state.compactOnIdleOverrides?.[k] === enabled)
1031
+ return;
1032
+ (this.state.compactOnIdleOverrides ??= {})[k] = enabled;
1033
+ this.scheduleSave();
1034
+ });
1035
+ }
1036
+ // ── full-summary toggle (`/compact_summary`) ──
1037
+ /**
1038
+ * @description The instance-wide full-compaction-summary default (driven from the
1039
+ * General topic). OFF when unset, unlike {@link getCompactOnIdleGlobalDefault}:
1040
+ * a compaction ends in one short line unless the summary is turned on.
1041
+ */
1042
+ getCompactSummaryGlobalDefault() {
1043
+ return this.state.compactSummaryEnabled ?? false;
1044
+ }
1045
+ /**
1046
+ * @description The per-thread full-summary override, or `undefined` when the
1047
+ * thread follows the instance default. A present value (true OR false) wins.
1048
+ */
1049
+ getCompactSummaryOverride(key) {
1050
+ return this.state.compactSummaryOverrides?.[(0, sessionKey_1.keyToString)(key)];
1051
+ }
1052
+ /**
1053
+ * @description Whether the full compaction summary is posted for `key`: the
1054
+ * per-thread override if set, else the instance default (ON when unset).
1055
+ */
1056
+ checkIsCompactSummaryEnabled(key) {
1057
+ return (0, compactOnIdle_1.resolveCompactSummaryEnabled)(this.state.compactSummaryEnabled, this.getCompactSummaryOverride(key));
1058
+ }
1059
+ /**
1060
+ * @description Set the instance-wide full-summary default (from General). Stored
1061
+ * EXPLICITLY as a boolean (incl. `false`) so a choice is durable and
1062
+ * distinguishable from "never set", which reads back as the OFF default. Debounced
1063
+ * (a preference, not crash-critical) — mirrors
1064
+ * {@link setCompactOnIdleGlobalDefault}.
1065
+ */
1066
+ async setCompactSummaryGlobalDefault(enabled) {
1067
+ if (this.state.compactSummaryEnabled === enabled)
1068
+ return;
1069
+ this.state.compactSummaryEnabled = enabled;
1070
+ this.scheduleSave();
1071
+ }
1072
+ /**
1073
+ * @description Set the per-thread full-summary override (from a regular topic).
1074
+ * Stored explicitly (a present true/false wins over the global default).
1075
+ * Debounced — mirrors {@link setCompactOnIdleOverride}.
1076
+ */
1077
+ async setCompactSummaryOverride(key, enabled) {
1078
+ const k = (0, sessionKey_1.keyToString)(key);
1079
+ await this.withLock(key, async () => {
1080
+ if (this.state.compactSummaryOverrides?.[k] === enabled)
1081
+ return;
1082
+ (this.state.compactSummaryOverrides ??= {})[k] = enabled;
1083
+ this.scheduleSave();
1084
+ });
1085
+ }
1086
+ /**
1087
+ * @description Whether `key`'s compact-on-idle user-latch is spent (D2): an
1088
+ * idle-compaction already fired this user-active period and no genuine USER
1089
+ * message has re-armed it since. Read by the idle watchdog's fire guard and by
1090
+ * the timer-arming path (a latched thread never re-arms — so a restart can't
1091
+ * re-fire). Default `false` (never latched).
1092
+ */
1093
+ checkIsCompactIdleLatched(key) {
1094
+ return this.state.compactIdleLatchedThreads?.includes((0, sessionKey_1.keyToString)(key)) ?? false;
1095
+ }
1096
+ /**
1097
+ * @description Set/clear `key`'s compact-on-idle user-latch (D2). Set on a
1098
+ * fire, cleared on a genuine USER message / fresh session start. Mirrors
1099
+ * {@link setTimestampsEnabled}'s shape discipline (deduped + sorted, dropped
1100
+ * when empty) and rides the debounced save loop — the persistence is only a
1101
+ * restart guard, so losing the last <=500ms is at worst one extra fire.
1102
+ */
1103
+ async setCompactIdleLatched(key, isLatched) {
1104
+ const keyStr = (0, sessionKey_1.keyToString)(key);
1105
+ const current = new Set(this.state.compactIdleLatchedThreads ?? []);
1106
+ if (isLatched)
1107
+ current.add(keyStr);
1108
+ else
1109
+ current.delete(keyStr);
1110
+ const uniqueKeys = [...current].sort();
1111
+ if (uniqueKeys.length > 0)
1112
+ this.state.compactIdleLatchedThreads = uniqueKeys;
1113
+ else
1114
+ delete this.state.compactIdleLatchedThreads;
1115
+ this.scheduleSave();
1116
+ }
1117
+ // ── compact-on-idle per-thread bookkeeping (the countdown's restart guard) ──
1118
+ /**
1119
+ * @description `key`'s persisted compact-on-idle instants, with every absent
1120
+ * field read back as `0`. `0` is the explicit "no history" value both readers
1121
+ * rely on: the arming path takes it as "use the full idle window" and the fire
1122
+ * guard's `lastTurnEndAt > lastCompactionAt` as "nothing to compress" — so a
1123
+ * pre-feature state file behaves exactly like an untracked thread.
1124
+ *
1125
+ * All zeros are ALSO returned whenever the entry does not belong to the thread's
1126
+ * CURRENT agent session (a different `sessionId`, or either id unknown): the
1127
+ * instants describe one session's context, so a session swap must reset the
1128
+ * marker, and deciding that here — rather than in a teardown handler — makes it
1129
+ * true regardless of which lifecycle events fired (a killed process emits none).
1130
+ * Unknown-means-zero is conservative in the right direction: at worst the topic
1131
+ * waits one more full idle window.
1132
+ */
1133
+ getCompactIdleTracking(key) {
1134
+ const k = (0, sessionKey_1.keyToString)(key);
1135
+ const entry = this.state.compactIdleTracking?.[k];
1136
+ const sessionId = this.getAgentSessionId(k);
1137
+ if (!entry || !sessionId || entry.sessionId !== sessionId) {
1138
+ return { lastActivityAt: 0, lastTurnEndAt: 0, lastCompactionAt: 0 };
1139
+ }
1140
+ return {
1141
+ lastActivityAt: entry.lastActivityAt ?? 0,
1142
+ lastTurnEndAt: entry.lastTurnEndAt ?? 0,
1143
+ lastCompactionAt: entry.lastCompactionAt ?? 0,
1144
+ };
1145
+ }
1146
+ /**
1147
+ * @description Stamp `key`'s last REAL activity — the instant the idle
1148
+ * countdown is measured from. Called on every agent output chunk, so the write
1149
+ * is COARSE: the in-memory value is always assigned (readers are exact), but a
1150
+ * save is scheduled only once the value has moved a full
1151
+ * {@link compactIdleTrackingPersistStepMs} past what was last persisted. The
1152
+ * assigned value also rides along for free on the next save the ~10s
1153
+ * {@link touchHeartbeat} loop schedules, so the coarse step costs nothing in
1154
+ * correctness — at worst the persisted stamp trails the live one by under a
1155
+ * minute, against a 55-minute threshold.
1156
+ */
1157
+ noteCompactIdleActivity(key, now = Date.now()) {
1158
+ this.stampCompactIdleTracking(key, 'lastActivityAt', now);
1159
+ }
1160
+ /**
1161
+ * @description Stamp that an agent turn produced output for `key` — the proof
1162
+ * there is something to compress (the fire guard compares this against
1163
+ * `lastCompactionAt`). Same per-chunk call frequency and therefore the same
1164
+ * coarse-save rule as {@link noteCompactIdleActivity}.
1165
+ */
1166
+ noteCompactIdleTurnEnd(key, now = Date.now()) {
1167
+ this.stampCompactIdleTracking(key, 'lastTurnEndAt', now);
1168
+ }
1169
+ /**
1170
+ * @description Stamp that `key` was just compacted (any trigger: manual
1171
+ * `/compact`, the F1 tool, an F2 idle fire), closing the fire guard until the
1172
+ * next turn produces output. Rare, so it always schedules a save — persisting it
1173
+ * is what stops a restart from re-compacting a session nothing has touched
1174
+ * since.
1175
+ */
1176
+ async setCompactIdleCompactedAt(key, now = Date.now()) {
1177
+ const k = (0, sessionKey_1.keyToString)(key);
1178
+ await this.withLock(key, async () => {
1179
+ const sessionId = this.getAgentSessionId(k);
1180
+ if (!sessionId)
1181
+ return; // nothing to bind the marker to — see `getAgentSessionId`
1182
+ const tracking = (this.state.compactIdleTracking ??= {});
1183
+ const existing = tracking[k];
1184
+ // A stamp under a DIFFERENT session starts the entry fresh: the previous
1185
+ // session's instants describe a context this one no longer has.
1186
+ const base = existing?.sessionId === sessionId ? existing : {};
1187
+ tracking[k] = { ...base, sessionId, lastCompactionAt: now };
1188
+ this.scheduleSave();
1189
+ });
1190
+ }
1191
+ /**
1192
+ * @description Drop `key`'s compact-on-idle bookkeeping (session teardown:
1193
+ * `/new`, `/quit`, unbind). Deliberately a full delete rather than a reset —
1194
+ * with no entry the next session start finds `lastActivityAt === 0` and gets
1195
+ * the FULL idle window, which is exactly the wanted behaviour for a fresh
1196
+ * session, so no special case is needed on the start path. The whole map is
1197
+ * dropped once empty so an idle bot leaves a clean `state.json`.
1198
+ */
1199
+ async clearCompactIdleTracking(key) {
1200
+ const k = (0, sessionKey_1.keyToString)(key);
1201
+ this.compactIdleTrackingSavedAt.delete(k);
1202
+ await this.withLock(key, async () => {
1203
+ if (!this.state.compactIdleTracking?.[k])
1204
+ return;
1205
+ delete this.state.compactIdleTracking[k];
1206
+ if (Object.keys(this.state.compactIdleTracking).length === 0) {
1207
+ delete this.state.compactIdleTracking;
1208
+ }
1209
+ this.scheduleSave();
1210
+ });
1211
+ }
1212
+ /**
1213
+ * Assign one high-frequency compact-on-idle stamp in memory and schedule a save
1214
+ * only past the coarse step (see {@link noteCompactIdleActivity}). Synchronous
1215
+ * (no key lock) on purpose: it runs on the output hot path, and a single-field
1216
+ * assignment cannot interleave with another write the way a read-modify-write
1217
+ * of a whole record can.
1218
+ */
1219
+ stampCompactIdleTracking(key, field, now) {
1220
+ const k = (0, sessionKey_1.keyToString)(key);
1221
+ const sessionId = this.getAgentSessionId(k);
1222
+ if (!sessionId)
1223
+ return; // nothing to bind the marker to — see `getAgentSessionId`
1224
+ const tracking = (this.state.compactIdleTracking ??= {});
1225
+ const existing = tracking[k];
1226
+ // A stamp under a DIFFERENT session REPLACES the entry rather than merging
1227
+ // into it — the old session's instants must not survive the swap.
1228
+ const isFreshSession = !existing || existing.sessionId !== sessionId;
1229
+ const entry = existing && !isFreshSession ? existing : { sessionId };
1230
+ tracking[k] = entry;
1231
+ entry[field] = now;
1232
+ // A fresh session's first stamp always persists, so the new binding reaches
1233
+ // disk instead of waiting out the coarse step behind a stale one.
1234
+ const savedAt = isFreshSession ? 0 : this.compactIdleTrackingSavedAt.get(k) ?? 0;
1235
+ if (now - savedAt < exports.compactIdleTrackingPersistStepMs)
1236
+ return;
1237
+ this.compactIdleTrackingSavedAt.set(k, now);
1238
+ this.scheduleSave();
1239
+ }
1240
+ /**
1241
+ * The thread's CURRENT agent session id — `claudeSessionId` else
1242
+ * `opencodeSessionId` from the persisted {@link AgentData} — or `undefined` when
1243
+ * the thread has neither. A raw `/terminal` shell has no session id and cannot be
1244
+ * compacted anyway, so "no id" means the compact-on-idle marker has nothing to
1245
+ * bind to: reads return zeros and stamps are skipped, keeping `state.json` clean.
1246
+ */
1247
+ getAgentSessionId(keyStr) {
1248
+ const agent = this.state.agents[keyStr];
1249
+ return agent?.claudeSessionId ?? agent?.opencodeSessionId;
1250
+ }
1251
+ // ── limit auto-continue toggle (`/auto_continue_limits`) ──
1252
+ /**
1253
+ * @description The instance-wide limit-auto-continue default (driven from the
1254
+ * General topic). ON when unset — the auto-resume was unconditional before the
1255
+ * toggle, so the default must preserve it.
1256
+ */
1257
+ getAutoContinueOnLimitGlobalDefault() {
1258
+ return this.state.autoContinueOnLimitEnabled ?? true;
1259
+ }
1260
+ /**
1261
+ * @description The per-thread limit-auto-continue override, or `undefined` when
1262
+ * the thread follows the instance default. A present value (true OR false) wins.
1263
+ */
1264
+ getAutoContinueOnLimitOverride(key) {
1265
+ return this.state.autoContinueOnLimitOverrides?.[(0, sessionKey_1.keyToString)(key)];
1266
+ }
1267
+ /**
1268
+ * @description Whether limit auto-continue is effectively enabled for `key`: the
1269
+ * per-thread override if set, else the instance default (ON when unset).
1270
+ */
1271
+ checkIsAutoContinueOnLimitEnabled(key) {
1272
+ return (0, autoContinueOnLimit_1.resolveAutoContinueOnLimitEnabled)(this.state.autoContinueOnLimitEnabled, this.getAutoContinueOnLimitOverride(key));
1273
+ }
1274
+ /**
1275
+ * @description Set the instance-wide limit-auto-continue default (from General).
1276
+ * Stored EXPLICITLY as a boolean (incl. `false`) so a «Disable» is durable and
1277
+ * distinguishable from "never set" (which reads back as the ON default) —
1278
+ * mirrors {@link setCompactOnIdleGlobalDefault}. Debounced (a preference, not
1279
+ * crash-critical).
1280
+ */
1281
+ async setAutoContinueOnLimitGlobalDefault(enabled) {
1282
+ if (this.state.autoContinueOnLimitEnabled === enabled)
1283
+ return;
1284
+ this.state.autoContinueOnLimitEnabled = enabled;
1285
+ this.scheduleSave();
1286
+ }
1287
+ /**
1288
+ * @description Set the per-thread limit-auto-continue override (from a regular
1289
+ * topic). Stored explicitly (a present true/false wins over the global
1290
+ * default); the map is created lazily on the first override, so an all-default
1291
+ * instance leaves a clean `state.json`. Debounced.
1292
+ */
1293
+ async setAutoContinueOnLimitOverride(key, enabled) {
1294
+ const k = (0, sessionKey_1.keyToString)(key);
1295
+ await this.withLock(key, async () => {
1296
+ if (enabled === null) {
1297
+ this.dropLimitOverride(this.state.autoContinueOnLimitOverrides, k, () => { delete this.state.autoContinueOnLimitOverrides; });
1298
+ return;
1299
+ }
1300
+ if (this.state.autoContinueOnLimitOverrides?.[k] === enabled) {
1301
+ return;
1302
+ }
1303
+ (this.state.autoContinueOnLimitOverrides ??= {})[k] = enabled;
1304
+ this.scheduleSave();
1305
+ });
1306
+ }
1307
+ /** Remove one thread's override from a toggle map (`null` enabled = back to the instance default); drops an emptied map. */
1308
+ dropLimitOverride(map, k, dropMap) {
1309
+ if (!map || !(k in map))
1310
+ return;
1311
+ delete map[k];
1312
+ if (Object.keys(map).length === 0)
1313
+ dropMap();
1314
+ this.scheduleSave();
1315
+ }
1316
+ // ── Claude account auto-switch on a usage limit (`/account`) ──
1317
+ /** The instance-wide auto-switch default (driven from General). OFF when unset: the switch is opt-in. */
1318
+ getClaudeAutoSwitchOnLimitGlobalDefault() {
1319
+ return this.state.claudeAutoSwitchOnLimitEnabled ?? false;
1320
+ }
1321
+ /** The per-thread auto-switch override, or `undefined` when the thread follows the instance default. */
1322
+ getClaudeAutoSwitchOnLimitOverride(key) {
1323
+ return this.state.claudeAutoSwitchOnLimitOverrides?.[(0, sessionKey_1.keyToString)(key)];
1324
+ }
1325
+ /** Whether the bot may move `key` to another Claude account by itself when its own hits a usage limit. */
1326
+ checkIsClaudeAutoSwitchOnLimitEnabled(key) {
1327
+ return (0, autoContinueOnLimit_1.resolveClaudeAutoSwitchOnLimitEnabled)(this.state.claudeAutoSwitchOnLimitEnabled, this.getClaudeAutoSwitchOnLimitOverride(key));
1328
+ }
1329
+ /** Set the instance-wide auto-switch default (from General); stored explicitly, like {@link setAutoContinueOnLimitGlobalDefault}. */
1330
+ async setClaudeAutoSwitchOnLimitGlobalDefault(enabled) {
1331
+ if (this.state.claudeAutoSwitchOnLimitEnabled === enabled)
1332
+ return;
1333
+ this.state.claudeAutoSwitchOnLimitEnabled = enabled;
1334
+ this.scheduleSave();
1335
+ }
1336
+ /** Set the per-thread auto-switch override (from a regular topic; `null` = follow the instance default again); the map is created on the first override. */
1337
+ async setClaudeAutoSwitchOnLimitOverride(key, enabled) {
1338
+ const k = (0, sessionKey_1.keyToString)(key);
1339
+ await this.withLock(key, async () => {
1340
+ if (enabled === null) {
1341
+ this.dropLimitOverride(this.state.claudeAutoSwitchOnLimitOverrides, k, () => { delete this.state.claudeAutoSwitchOnLimitOverrides; });
1342
+ return;
1343
+ }
1344
+ if (this.state.claudeAutoSwitchOnLimitOverrides?.[k] === enabled)
1345
+ return;
1346
+ (this.state.claudeAutoSwitchOnLimitOverrides ??= {})[k] = enabled;
1347
+ this.scheduleSave();
1348
+ });
1349
+ }
1350
+ // ── the fallback chain (`/fallback`) ──
1351
+ /** The chain as persisted, validated entry by entry; `[]` when none was set. */
1352
+ getFallbackChain() {
1353
+ return (0, fallbackChain_1.normaliseFallbackChain)(this.state.fallbackChain);
1354
+ }
1355
+ /** Replace the chain (an empty one is removed from the file). Flushed at once: the picker re-reads it right after. */
1356
+ async setFallbackChain(chain) {
1357
+ const next = (0, fallbackChain_1.normaliseFallbackChain)(chain);
1358
+ if (next.length === 0)
1359
+ delete this.state.fallbackChain;
1360
+ else
1361
+ this.state.fallbackChain = next;
1362
+ await this.flush();
1363
+ }
1364
+ /** The limit marks of OpenCode models (`provider/model` → ISO instant), expired ones included. */
1365
+ getOpenCodeModelLimits() {
1366
+ return this.state.openCodeModelLimits ?? {};
1367
+ }
1368
+ getOpenCodeModelLimitedUntil(label) {
1369
+ return this.state.openCodeModelLimits?.[label];
1370
+ }
1371
+ /** Mark an OpenCode model limited until the instant (`null` clears). Debounced. */
1372
+ setOpenCodeModelLimitedUntil(label, limitedUntil) {
1373
+ if (limitedUntil === null) {
1374
+ if (!this.state.openCodeModelLimits?.[label])
1375
+ return;
1376
+ delete this.state.openCodeModelLimits[label];
1377
+ if (Object.keys(this.state.openCodeModelLimits).length === 0)
1378
+ delete this.state.openCodeModelLimits;
1379
+ }
1380
+ else {
1381
+ (this.state.openCodeModelLimits ??= {})[label] = limitedUntil;
1382
+ }
1383
+ this.scheduleSave();
1384
+ }
1385
+ // ── Claude account profiles (`/account`) ──
1386
+ /** The added profiles by name — `default` is never among them (its hidden record is `getClaudeProfileInfo`'s). */
1387
+ getClaudeProfileRecords() {
1388
+ const records = {};
1389
+ for (const [name, record] of Object.entries(this.state.claudeProfiles ?? {})) {
1390
+ if (name !== claudeProfiles_1.defaultClaudeProfileName)
1391
+ records[name] = record;
1392
+ }
1393
+ return records;
1394
+ }
1395
+ /** An ADDED profile's record; `undefined` for an unknown name and for `default`. */
1396
+ getClaudeProfileRecord(name) {
1397
+ return name === claudeProfiles_1.defaultClaudeProfileName ? undefined : this.state.claudeProfiles?.[name];
1398
+ }
1399
+ /** The names the resolver treats as known: the added profiles (`default` is implicit). */
1400
+ getClaudeProfileNames() {
1401
+ return new Set(Object.keys(this.getClaudeProfileRecords()));
1402
+ }
1403
+ /** What is known about ANY profile, `default` included (its probe result, usage snapshot, limit mark). */
1404
+ getClaudeProfileInfo(name) {
1405
+ return this.state.claudeProfiles?.[name];
1406
+ }
1407
+ /** Every record, the hidden `default` one included — the usage ranking's input. */
1408
+ getAllClaudeProfileInfos() {
1409
+ return this.state.claudeProfiles ?? {};
1410
+ }
1411
+ /**
1412
+ * @description Store a profile's usage snapshot (debounced: a stream frame may update it every API request).
1413
+ * `default` gets its hidden record on first write; an unknown added name is ignored (removed meanwhile).
1414
+ */
1415
+ setClaudeProfileUsage(name, usage) {
1416
+ const record = this.touchClaudeProfileInfo(name);
1417
+ if (!record)
1418
+ return;
1419
+ record.usage = usage;
1420
+ this.scheduleSave();
1421
+ }
1422
+ /** The limit mark (`null` clears it); same record rules as the usage. Debounced. */
1423
+ setClaudeProfileLimitedUntil(name, limitedUntil) {
1424
+ const record = this.touchClaudeProfileInfo(name);
1425
+ if (!record)
1426
+ return;
1427
+ if (limitedUntil === null)
1428
+ delete record.limitedUntil;
1429
+ else
1430
+ record.limitedUntil = limitedUntil;
1431
+ this.scheduleSave();
1432
+ }
1433
+ /** The mutable record behind `name`: created for `default`, `null` for an added name that is not registered. */
1434
+ touchClaudeProfileInfo(name) {
1435
+ const existing = this.state.claudeProfiles?.[name];
1436
+ if (existing)
1437
+ return existing;
1438
+ if (name !== claudeProfiles_1.defaultClaudeProfileName)
1439
+ return null;
1440
+ const created = { createdAt: new Date().toISOString() };
1441
+ (this.state.claudeProfiles ??= {})[name] = created;
1442
+ return created;
1443
+ }
1444
+ /**
1445
+ * @description Add or update a profile record (merge; `createdAt` is kept once written). Flushed
1446
+ * immediately rather than debounced: the record is the registry entry of a directory that already holds
1447
+ * a login, and a crash between the two would leave an orphan directory the picker never shows.
1448
+ */
1449
+ async upsertClaudeProfileRecord(name, patch) {
1450
+ const existing = this.state.claudeProfiles?.[name];
1451
+ (this.state.claudeProfiles ??= {})[name] = {
1452
+ ...existing,
1453
+ ...patch,
1454
+ createdAt: existing?.createdAt ?? patch.createdAt ?? new Date().toISOString(),
1455
+ };
1456
+ await this.flush();
1457
+ }
1458
+ /**
1459
+ * @description Drop a profile from the registry together with every per-thread pick of it (those threads
1460
+ * return to the instance default) and the instance default itself when it named this profile. Returns the
1461
+ * threads whose pick was dropped, so the caller can re-point their live sessions.
1462
+ */
1463
+ async removeClaudeProfileRecord(name) {
1464
+ const dropped = [];
1465
+ if (this.state.claudeProfiles) {
1466
+ delete this.state.claudeProfiles[name];
1467
+ if (Object.keys(this.state.claudeProfiles).length === 0)
1468
+ delete this.state.claudeProfiles;
1469
+ }
1470
+ if (this.state.claudeProfileDefault === name)
1471
+ delete this.state.claudeProfileDefault;
1472
+ for (const [keyStr, picked] of Object.entries(this.state.claudeProfileOverrides ?? {})) {
1473
+ if (picked !== name)
1474
+ continue;
1475
+ delete this.state.claudeProfileOverrides[keyStr];
1476
+ // A key of an unregistered platform is dropped from the map; there is nothing to re-point.
1477
+ const key = (0, sessionKey_1.tryKeyFromString)(keyStr);
1478
+ if (key)
1479
+ dropped.push(key);
1480
+ }
1481
+ if (this.state.claudeProfileOverrides && Object.keys(this.state.claudeProfileOverrides).length === 0) {
1482
+ delete this.state.claudeProfileOverrides;
1483
+ }
1484
+ await this.flush();
1485
+ return dropped;
1486
+ }
1487
+ /** The instance-wide profile pick (General), `default` when none was ever set. */
1488
+ getClaudeProfileGlobalDefault() {
1489
+ return (0, claudeProfiles_1.resolveClaudeProfileName)({
1490
+ globalDefault: this.state.claudeProfileDefault,
1491
+ threadOverride: undefined,
1492
+ knownNames: this.getClaudeProfileNames(),
1493
+ });
1494
+ }
1495
+ /** The thread's own pick as stored (it may name a removed profile), or `undefined` when it follows the default. */
1496
+ getClaudeProfileOverride(key) {
1497
+ return this.state.claudeProfileOverrides?.[(0, sessionKey_1.keyToString)(key)];
1498
+ }
1499
+ /** The profile the thread effectively uses — override, then instance default, then `default`. */
1500
+ resolveClaudeProfileName(key) {
1501
+ return (0, claudeProfiles_1.resolveClaudeProfileName)({
1502
+ globalDefault: this.state.claudeProfileDefault,
1503
+ threadOverride: this.getClaudeProfileOverride(key),
1504
+ knownNames: this.getClaudeProfileNames(),
1505
+ });
1506
+ }
1507
+ /** Threads whose stored pick names `name` (General's default is not a thread). */
1508
+ listClaudeProfileOverrideKeys(name) {
1509
+ const keys = [];
1510
+ for (const [keyStr, picked] of Object.entries(this.state.claudeProfileOverrides ?? {})) {
1511
+ if (picked !== name)
1512
+ continue;
1513
+ const key = (0, sessionKey_1.tryKeyFromString)(keyStr);
1514
+ if (key)
1515
+ keys.push(key);
1516
+ }
1517
+ return keys;
1518
+ }
1519
+ /** Set the instance-wide pick from General (`default` clears it). Debounced — a preference. */
1520
+ async setClaudeProfileGlobalDefault(name) {
1521
+ const next = name === claudeProfiles_1.defaultClaudeProfileName ? undefined : name;
1522
+ if (this.state.claudeProfileDefault === next)
1523
+ return;
1524
+ if (next === undefined)
1525
+ delete this.state.claudeProfileDefault;
1526
+ else
1527
+ this.state.claudeProfileDefault = next;
1528
+ this.scheduleSave();
1529
+ }
1530
+ /**
1531
+ * @description Set the thread's own pick; `null` makes the thread follow the instance default again. A pick
1532
+ * of `default` is stored EXPLICITLY (it must win over a non-default instance default). Debounced.
1533
+ */
1534
+ async setClaudeProfileOverride(key, name) {
1535
+ const k = (0, sessionKey_1.keyToString)(key);
1536
+ await this.withLock(key, async () => {
1537
+ if ((this.state.claudeProfileOverrides?.[k] ?? null) === name)
1538
+ return;
1539
+ if (name === null) {
1540
+ delete this.state.claudeProfileOverrides?.[k];
1541
+ if (this.state.claudeProfileOverrides && Object.keys(this.state.claudeProfileOverrides).length === 0) {
1542
+ delete this.state.claudeProfileOverrides;
1543
+ }
1544
+ }
1545
+ else {
1546
+ (this.state.claudeProfileOverrides ??= {})[k] = name;
1547
+ }
1548
+ this.scheduleSave();
1549
+ });
1550
+ }
1551
+ // ── instance-wide timezone (`/timezone`) ──
1552
+ /**
1553
+ * @description The stored operator timezone, or `null` when none is set (the
1554
+ * caller then falls back to the host zone — see `utils/timezone.ts`
1555
+ * `getEffectiveTimezone`). Deliberately NOT defaulted here: the store reports
1556
+ * what is on disk, the resolution rule lives in one place outside it.
1557
+ */
1558
+ getTimezone() {
1559
+ return this.state.timezone ?? null;
1560
+ }
1561
+ /**
1562
+ * @description Store the operator timezone, or drop the field on `null`
1563
+ * (reset to the host zone) so a default install leaves no trace in
1564
+ * `state.json` — same shape discipline as {@link setModelProviderHidden}.
1565
+ *
1566
+ * Flushed eagerly rather than riding the debounced save loop: a zone change
1567
+ * immediately recomputes every schedule's `nextRunAt`, and losing the zone
1568
+ * while keeping the recomputed fire times would leave the two disagreeing
1569
+ * after a crash.
1570
+ */
1571
+ async setTimezone(timezone) {
1572
+ if (timezone === null)
1573
+ delete this.state.timezone;
1574
+ else
1575
+ this.state.timezone = timezone;
1576
+ await this.flush();
1577
+ }
1578
+ // ── per-thread display preferences (`/thinking`, `/tool-results`, `/subagent`) ──
1579
+ /**
1580
+ * @description Fully-resolved display preferences for `key`: every field is
1581
+ * present because the locked default (`minimal` for all three) is applied
1582
+ * wherever the persisted record omits it. The SSE hot path reads this, so it
1583
+ * must never hand back an `undefined` field.
1584
+ *
1585
+ * Read-time normalization: a `state.json` written before the vocabulary was
1586
+ * unified may still hold legacy names (`detailed`/`brief`/`hide`/`compact`)
1587
+ * — those map onto the new vocabulary here, and an unrecognized value falls
1588
+ * back to the default, so old files keep working without a migration pass.
1589
+ */
1590
+ getDisplayPrefs(key) {
1591
+ const stored = this.state.displayPrefs?.[(0, sessionKey_1.keyToString)(key)];
1592
+ return {
1593
+ thinking: (0, displayVerbosity_1.normalizeDisplayVerbosityMode)(stored?.thinking) ?? displayVerbosity_1.defaultDisplayVerbosityMode,
1594
+ toolResults: (0, displayVerbosity_1.normalizeDisplayVerbosityMode)(stored?.toolResults) ?? displayVerbosity_1.defaultDisplayVerbosityMode,
1595
+ subagent: (0, displayVerbosity_1.normalizeDisplayVerbosityMode)(stored?.subagent) ?? displayVerbosity_1.defaultDisplayVerbosityMode,
1596
+ view: (0, topicView_1.parseTopicView)(stored?.view) ?? topicView_1.defaultTopicView,
1597
+ };
1598
+ }
1599
+ /**
1600
+ * @description Persist one display-preference field for `key`. Setting a
1601
+ * field to its locked default DELETES the override (and drops the whole
1602
+ * record / map once empty) so an all-defaults thread leaves a clean
1603
+ * `state.json` — same delete-when-default idiom as {@link setTraceConfig}.
1604
+ * Not crash-critical (a rendering preference), so it rides the debounced
1605
+ * save loop rather than an immediate `flush()`. Callers pass an
1606
+ * already-normalized value — legacy names never reach the store on the write
1607
+ * path. The `view` field (S6) rides the same record and the same idiom, with
1608
+ * its own locked default.
1609
+ */
1610
+ async setDisplayPref(key, field, value) {
1611
+ const k = (0, sessionKey_1.keyToString)(key);
1612
+ await this.withLock(key, async () => {
1613
+ const isDefault = value === (field === 'view' ? topicView_1.defaultTopicView : displayVerbosity_1.defaultDisplayVerbosityMode);
1614
+ const existing = this.state.displayPrefs?.[k];
1615
+ if (isDefault) {
1616
+ // Clear the override; drop the record (and the map) once empty so an
1617
+ // all-defaults thread leaves no trace in state.json.
1618
+ if (!existing || existing[field] === undefined)
1619
+ return;
1620
+ const { [field]: _drop, ...rest } = existing;
1621
+ if (Object.keys(rest).length === 0) {
1622
+ delete this.state.displayPrefs[k];
1623
+ if (Object.keys(this.state.displayPrefs).length === 0) {
1624
+ delete this.state.displayPrefs;
1625
+ }
1626
+ }
1627
+ else {
1628
+ this.state.displayPrefs[k] = rest;
1629
+ }
1630
+ this.scheduleSave();
1631
+ return;
1632
+ }
1633
+ if (existing?.[field] === value)
1634
+ return;
1635
+ (this.state.displayPrefs ??= {})[k] = { ...existing, [field]: value };
1636
+ this.scheduleSave();
1637
+ });
1638
+ }
1639
+ // ── scheduled jobs (`/schedule`) ──
1640
+ /**
1641
+ * @description Every persisted schedule across all threads, keyed by id.
1642
+ * Read at boot by the engine to re-arm timers. Returns a shallow copy of the
1643
+ * map so callers can't mutate the live state object.
1644
+ */
1645
+ getSchedules() {
1646
+ return { ...(this.state.schedules ?? {}) };
1647
+ }
1648
+ /** Schedules owned by `key`, in insertion order. Empty array when none. */
1649
+ getThreadSchedules(key) {
1650
+ const target = (0, sessionKey_1.keyToString)(key);
1651
+ const all = this.state.schedules ?? {};
1652
+ return Object.values(all).filter(record => record.threadKey === target);
1653
+ }
1654
+ /**
1655
+ * @description Upsert a schedule by its id. The record's `threadKey` is the
1656
+ * source of truth for ownership; callers build it via the scheduler module's
1657
+ * `createScheduleRecord`. Crash-critical fields (`nextRunAt`) ride the
1658
+ * debounced save like every other mutation — the engine calls
1659
+ * {@link flush} explicitly after a fire when the new `nextRunAt` must hit disk
1660
+ * before the process can die.
1661
+ */
1662
+ async upsertSchedule(record) {
1663
+ const target = (0, sessionKey_1.tryKeyFromString)(record.threadKey);
1664
+ if (!target)
1665
+ return;
1666
+ await this.withLock(target, async () => {
1667
+ (this.state.schedules ??= {})[record.id] = record;
1668
+ this.scheduleSave();
1669
+ });
1670
+ }
1671
+ /** Remove a schedule by id. No-op (no save) when absent. */
1672
+ async removeSchedule(id) {
1673
+ const existing = this.state.schedules?.[id];
1674
+ if (!existing)
1675
+ return;
1676
+ const owner = (0, sessionKey_1.tryKeyFromString)(existing.threadKey);
1677
+ const run = async () => {
1678
+ if (this.state.schedules) {
1679
+ delete this.state.schedules[id];
1680
+ if (Object.keys(this.state.schedules).length === 0)
1681
+ delete this.state.schedules;
1682
+ }
1683
+ this.scheduleSave();
1684
+ };
1685
+ if (owner)
1686
+ await this.withLock(owner, run);
1687
+ else
1688
+ await run();
1689
+ }
1690
+ /**
1691
+ * @description Set the paused flag for a schedule (used by /unbind pause and
1692
+ * /bind resume in S8). Clearing the pause drops both `isPaused` and
1693
+ * `pauseReason` so the on-disk shape stays clean. No-op when the id is absent.
1694
+ */
1695
+ async setSchedulePaused(id, paused, reason) {
1696
+ const existing = this.state.schedules?.[id];
1697
+ if (!existing)
1698
+ return;
1699
+ const owner = (0, sessionKey_1.tryKeyFromString)(existing.threadKey);
1700
+ const run = async () => {
1701
+ const current = this.state.schedules?.[id];
1702
+ if (!current)
1703
+ return;
1704
+ if (paused) {
1705
+ current.isPaused = true;
1706
+ if (reason !== undefined)
1707
+ current.pauseReason = reason;
1708
+ }
1709
+ else {
1710
+ delete current.isPaused;
1711
+ delete current.pauseReason;
1712
+ }
1713
+ current.updatedAt = new Date().toISOString();
1714
+ this.scheduleSave();
1715
+ };
1716
+ if (owner)
1717
+ await this.withLock(owner, run);
1718
+ else
1719
+ await run();
1720
+ }
1721
+ // ── scheduler MCP secret ──
1722
+ /**
1723
+ * @description The HMAC secret keying the scheduler MCP tokens, created lazily
1724
+ * on first read and persisted immediately. Generated once with
1725
+ * `crypto.randomBytes` (32 bytes, hex) so the tokens the bot injects into
1726
+ * agent sessions survive restarts. The first-time write is flushed eagerly
1727
+ * (not debounced) so a crash right after minting the first token can't lose
1728
+ * the secret that token was signed with.
1729
+ */
1730
+ async getSchedulerMcpSecret() {
1731
+ if (this.state.schedulerMcpSecret)
1732
+ return this.state.schedulerMcpSecret;
1733
+ const secret = (0, node_crypto_1.randomBytes)(schedulerMcpSecretByteLength).toString('hex');
1734
+ this.state.schedulerMcpSecret = secret;
1735
+ await this.flush();
1736
+ return secret;
1737
+ }
1738
+ // ── scheduler MCP port (reuse across restarts) ──
1739
+ /**
1740
+ * @description The scheduler MCP listen port persisted from a prior boot, or
1741
+ * `undefined` if never persisted (fresh / pre-feature state file). Reused so an
1742
+ * ephemeral port keeps its value across restarts (see
1743
+ * {@link StateV1.schedulerMcpPort}).
1744
+ */
1745
+ getPersistedSchedulerMcpPort() {
1746
+ return this.state.schedulerMcpPort;
1747
+ }
1748
+ /**
1749
+ * @description Persist the actually-bound scheduler MCP port and flush eagerly.
1750
+ * Eager (not debounced) mirrors {@link getSchedulerMcpSecret}: the injected
1751
+ * registrations point at this port, so a crash right after binding must not lose
1752
+ * it and hand the next boot a stale port. Called once at boot, only when the
1753
+ * value changed and the operator did not fix the port via env.
1754
+ */
1755
+ async setSchedulerMcpPort(port) {
1756
+ this.state.schedulerMcpPort = port;
1757
+ await this.flush();
1758
+ }
1759
+ // ── pending interactive questions (restart survival) ──
1760
+ /**
1761
+ * @description Every persisted pending question across all threads, keyed by
1762
+ * {@link SessionKey} string. Read at boot to re-arm the in-memory map for
1763
+ * threads whose session reattached. Returns a shallow copy so callers can't
1764
+ * mutate the live state object.
1765
+ */
1766
+ getPendingQuestions() {
1767
+ return { ...(this.state.pendingQuestions ?? {}) };
1768
+ }
1769
+ /**
1770
+ * @description Persist (upsert) the pending question for `key`, mirroring the
1771
+ * in-memory `pendingQuestions.set(...)` in `bot.ts` so memory and disk never
1772
+ * drift. Crash-window is acceptable on the debounced save: the worst case
1773
+ * (process dies in the <=500ms before the flush) just falls back to the
1774
+ * pre-fix behaviour for that one question. The `messageId` patch goes through
1775
+ * here too, so the persisted record always carries the live button message.
1776
+ */
1777
+ async setPendingQuestion(key, value) {
1778
+ const k = (0, sessionKey_1.keyToString)(key);
1779
+ await this.withLock(key, async () => {
1780
+ (this.state.pendingQuestions ??= {})[k] = value;
1781
+ this.scheduleSave();
1782
+ });
1783
+ }
1784
+ /**
1785
+ * @description Remove the pending question for `key`. Mirrors every in-memory
1786
+ * `pendingQuestions.delete(...)` in `bot.ts`. No-op (no save) when absent. The
1787
+ * whole map is dropped once empty so an idle bot leaves a clean `state.json`.
1788
+ */
1789
+ async clearPendingQuestion(key) {
1790
+ const k = (0, sessionKey_1.keyToString)(key);
1791
+ await this.withLock(key, async () => {
1792
+ if (!this.state.pendingQuestions?.[k])
1793
+ return;
1794
+ delete this.state.pendingQuestions[k];
1795
+ if (Object.keys(this.state.pendingQuestions).length === 0) {
1796
+ delete this.state.pendingQuestions;
1797
+ }
1798
+ this.scheduleSave();
1799
+ });
1800
+ }
1801
+ // ── armed API-error auto-retries (restart survival) ──
1802
+ /**
1803
+ * @description Every persisted armed auto-retry across all threads, keyed by
1804
+ * {@link SessionKey} string. Read at boot to re-arm the in-memory map after
1805
+ * sessions reattach. Returns a shallow copy so callers can't mutate the live
1806
+ * state object.
1807
+ */
1808
+ getApiRetries() {
1809
+ return { ...(this.state.apiRetries ?? {}) };
1810
+ }
1811
+ /**
1812
+ * @description Persist (upsert) the armed retry for `key`, mirroring the
1813
+ * in-memory `apiRetries.set(...)` in `bot.ts` so memory and disk never drift.
1814
+ * Crash-window is acceptable on the debounced save: the worst case (process
1815
+ * dies in the <=500ms before the flush) just loses that one armed retry, which
1816
+ * falls back to the pre-fix behaviour for that thread. Each re-arm (next
1817
+ * attempt) goes through here too, so the persisted record always carries the
1818
+ * latest attempt and `fireAt`.
1819
+ */
1820
+ async setApiRetry(key, value) {
1821
+ const k = (0, sessionKey_1.keyToString)(key);
1822
+ await this.withLock(key, async () => {
1823
+ (this.state.apiRetries ??= {})[k] = value;
1824
+ this.scheduleSave();
1825
+ });
1826
+ }
1827
+ /**
1828
+ * @description Remove the armed retry for `key`. Mirrors every in-memory
1829
+ * `apiRetries.delete(...)` in `bot.ts`. No-op (no save) when absent. The whole
1830
+ * map is dropped once empty so an idle bot leaves a clean `state.json`.
1831
+ */
1832
+ async clearApiRetry(key) {
1833
+ const k = (0, sessionKey_1.keyToString)(key);
1834
+ await this.withLock(key, async () => {
1835
+ if (!this.state.apiRetries?.[k])
1836
+ return;
1837
+ delete this.state.apiRetries[k];
1838
+ if (Object.keys(this.state.apiRetries).length === 0) {
1839
+ delete this.state.apiRetries;
1840
+ }
1841
+ this.scheduleSave();
1842
+ });
1843
+ }
1844
+ // ── prompts held during a usage-limit wait (R23) ──
1845
+ /** @description The prompts held for `key`, oldest first; `[]` when none. A copy. */
1846
+ getLimitHeldPrompts(key) {
1847
+ return (this.state.limitHeldPrompts?.[(0, sessionKey_1.keyToString)(key)] ?? []).map((prompt) => ({ ...prompt }));
1848
+ }
1849
+ /**
1850
+ * @description Replace the prompts held for `key`; `[]` drops the entry (and
1851
+ * the map once empty). Synchronous in memory — a take must be atomic against
1852
+ * a concurrent hold — and saved debounced like the retry record it outlives.
1853
+ */
1854
+ setLimitHeldPrompts(key, prompts) {
1855
+ const k = (0, sessionKey_1.keyToString)(key);
1856
+ if (prompts.length > 0) {
1857
+ (this.state.limitHeldPrompts ??= {})[k] = prompts.map((prompt) => ({ ...prompt }));
1858
+ }
1859
+ else {
1860
+ if (!this.state.limitHeldPrompts?.[k])
1861
+ return;
1862
+ delete this.state.limitHeldPrompts[k];
1863
+ if (Object.keys(this.state.limitHeldPrompts).length === 0)
1864
+ delete this.state.limitHeldPrompts;
1865
+ }
1866
+ this.scheduleSave();
1867
+ }
1868
+ // ── open requests (request ledger) ──
1869
+ /**
1870
+ * @description Every open request across all conversations, keyed by the
1871
+ * serialized {@link RequestGroupKey}. Returns a shallow copy so callers can't
1872
+ * mutate the live state object.
1873
+ */
1874
+ getOpenRequests() {
1875
+ return { ...(this.state.openRequests ?? {}) };
1876
+ }
1877
+ /** @description The open request of `group`, or `undefined`. */
1878
+ getOpenRequest(group) {
1879
+ return this.state.openRequests?.[(0, requestGroup_1.requestGroupKeyToString)(group)];
1880
+ }
1881
+ /**
1882
+ * @description Replace the open request of `group` with what `mutate` returns
1883
+ * for the current one (`undefined` removes it), under the lock of the group's
1884
+ * CONVERSATION — so a read-decide-write (supersede, close, counter update) is
1885
+ * atomic against a concurrent one for the same conversation, whichever of its
1886
+ * groups it touches. `mutate` runs
1887
+ * synchronously inside the lock; a side effect it performs (the ledger's
1888
+ * history append) therefore happens before any other writer sees the result.
1889
+ * The whole map is dropped once empty so an idle bot leaves a clean
1890
+ * `state.json`.
1891
+ */
1892
+ async updateOpenRequest(group, mutate) {
1893
+ const k = (0, requestGroup_1.requestGroupKeyToString)(group);
1894
+ await this.withLock(group.conversation, async () => {
1895
+ const current = this.state.openRequests?.[k];
1896
+ const next = mutate(current);
1897
+ if (next === current)
1898
+ return;
1899
+ if (next === undefined) {
1900
+ if (!this.dropOptionalMapEntry('openRequests', k))
1901
+ return;
1902
+ }
1903
+ else {
1904
+ (this.state.openRequests ??= {})[k] = next;
1905
+ }
1906
+ this.scheduleSave();
1907
+ });
1908
+ }
1909
+ /** @description The alerts of closed requests still to release (a copy). */
1910
+ getUnreleasedRequestAlerts() {
1911
+ return { ...(this.state.unreleasedRequestAlerts ?? {}) };
1912
+ }
1913
+ /**
1914
+ * @description Record that a closed request's alert still needs releasing.
1915
+ * Synchronous on purpose: the ledger calls it inside the close's own locked
1916
+ * step, so it rides the same save as the close.
1917
+ */
1918
+ addUnreleasedRequestAlert(requestId, alert) {
1919
+ (this.state.unreleasedRequestAlerts ??= {})[requestId] = alert;
1920
+ this.scheduleSave();
1921
+ }
1922
+ /** @description The alert of `requestId` was released; forget it. */
1923
+ removeUnreleasedRequestAlert(requestId) {
1924
+ if (!this.state.unreleasedRequestAlerts?.[requestId])
1925
+ return;
1926
+ delete this.state.unreleasedRequestAlerts[requestId];
1927
+ if (Object.keys(this.state.unreleasedRequestAlerts).length === 0)
1928
+ delete this.state.unreleasedRequestAlerts;
1929
+ this.scheduleSave();
1930
+ }
1931
+ // ── pinned answers (S8) ──
1932
+ /** @description The message id of the latest pinned answer in `key`'s conversation, or `undefined`. */
1933
+ getAnswerPinMessageId(key) {
1934
+ return this.state.answerPins?.[(0, sessionKey_1.keyToString)(key)];
1935
+ }
1936
+ /**
1937
+ * @description Remember the latest pinned answer of `key`'s conversation; the
1938
+ * record goes away with the thread's binding. Rides the debounced save: a lost
1939
+ * write leaves one extra pinned answer behind, never a wrong unpin.
1940
+ */
1941
+ async setAnswerPinMessageId(key, messageId) {
1942
+ const k = (0, sessionKey_1.keyToString)(key);
1943
+ await this.withLock(key, async () => {
1944
+ if (this.state.answerPins?.[k] === messageId)
1945
+ return;
1946
+ (this.state.answerPins ??= {})[k] = messageId;
1947
+ this.scheduleSave();
1948
+ });
1949
+ }
1950
+ /**
1951
+ * Drop `k` from one of the optional per-conversation maps, and the map itself
1952
+ * with its last entry (an idle bot leaves a clean `state.json`). `false` when
1953
+ * there was no entry. Caller must hold the key lock.
1954
+ */
1955
+ dropOptionalMapEntry(field, k) {
1956
+ const map = this.state[field];
1957
+ if (map?.[k] === undefined)
1958
+ return false;
1959
+ delete map[k];
1960
+ if (Object.keys(map).length === 0)
1961
+ delete this.state[field];
1962
+ return true;
1963
+ }
1964
+ // ── handled limit episodes (boot-recovery marker) ──
1965
+ /**
1966
+ * @description The `stdout.jsonl` identity recorded when the boot recovery last
1967
+ * armed a limit wait for `key`, or `undefined` when it never did. Compared
1968
+ * against the live stat so an UNCHANGED log is not recovered twice — see
1969
+ * {@link LimitEpisodeMarker}.
1970
+ */
1971
+ getLimitEpisodeRecovered(key) {
1972
+ return this.state.limitEpisodesRecovered?.[(0, sessionKey_1.keyToString)(key)];
1973
+ }
1974
+ /**
1975
+ * @description Record the log identity the boot recovery just armed from.
1976
+ * Debounced (a restart guard, not crash-critical: losing the last <=500ms costs
1977
+ * at worst one repeated recovery). NOT cleared by `cancelApiRetry` on purpose —
1978
+ * that fires on «⏭ Skip once» and on every inbound message, which is exactly the
1979
+ * settled-episode case the marker has to outlive.
1980
+ */
1981
+ async setLimitEpisodeRecovered(key, value) {
1982
+ const k = (0, sessionKey_1.keyToString)(key);
1983
+ await this.withLock(key, async () => {
1984
+ (this.state.limitEpisodesRecovered ??= {})[k] = value;
1985
+ this.scheduleSave();
1986
+ });
1987
+ }
1988
+ /**
1989
+ * Drop `keyStr`'s handled-episode marker (and the whole map once empty). Returns
1990
+ * whether anything changed. Caller must hold the key lock.
1991
+ */
1992
+ dropLimitEpisodeRecovered(keyStr) {
1993
+ return this.dropOptionalMapEntry('limitEpisodesRecovered', keyStr);
1994
+ }
1995
+ // ── transient status-frame ids (restart cleanup) ──
1996
+ /**
1997
+ * @description Every persisted transient status-frame id list across all
1998
+ * threads, keyed by {@link SessionKey} string. Read at boot (after reattach) to
1999
+ * delete any frame left on screen by an UNGRACEFUL exit. Returns a shallow copy
2000
+ * so callers can't mutate the live state object.
2001
+ */
2002
+ getTransientFrames() {
2003
+ return { ...(this.state.transientFrames ?? {}) };
2004
+ }
2005
+ /**
2006
+ * @description Replace the persisted transient status-frame ids for `key`
2007
+ * (the live "✽ working…" status frame, the thinking indicator, the sub-agent
2008
+ * status — whichever are currently on screen). Mirrors the in-memory
2009
+ * `ThreadMessageState` write in `bot.ts`'s frame-id setters so disk can clean
2010
+ * up what the graceful sweep would have. Debounced (NOT flushed): the frames
2011
+ * are cosmetic. An empty list drops the key (and the whole map once empty) so an
2012
+ * idle bot leaves a clean `state.json`. A no-op (no save) when the id list is
2013
+ * unchanged, so the per-turn create/delete churn doesn't reset the save timer
2014
+ * on every identical re-persist.
2015
+ */
2016
+ async setTransientFrames(key, ids) {
2017
+ const k = (0, sessionKey_1.keyToString)(key);
2018
+ await this.withLock(key, async () => {
2019
+ const existing = this.state.transientFrames?.[k];
2020
+ if (ids.length === 0) {
2021
+ if (!existing)
2022
+ return;
2023
+ delete this.state.transientFrames[k];
2024
+ if (Object.keys(this.state.transientFrames).length === 0) {
2025
+ delete this.state.transientFrames;
2026
+ }
2027
+ this.scheduleSave();
2028
+ return;
2029
+ }
2030
+ if (existing && existing.length === ids.length && existing.every((v, i) => v === ids[i])) {
2031
+ return;
2032
+ }
2033
+ (this.state.transientFrames ??= {})[k] = ids.slice();
2034
+ this.scheduleSave();
2035
+ });
2036
+ }
2037
+ // ── persistence ──
2038
+ /** Schedule a save in `saveDebounceMs`. Repeated calls reset the timer. */
2039
+ scheduleSave() {
2040
+ if (this.saveTimer)
2041
+ clearTimeout(this.saveTimer);
2042
+ this.saveTimer = setTimeout(() => {
2043
+ this.saveTimer = null;
2044
+ // Errors are logged inside flush; we don't want to crash the timer chain.
2045
+ this.flush().catch(e => console.error('[state] background flush failed:', e));
2046
+ }, this.saveDebounceMs);
2047
+ }
2048
+ /**
2049
+ * @description Force a save right now and await on-disk persistence.
2050
+ *
2051
+ * Used by:
2052
+ * - shutdown handlers — to guarantee no in-memory state is lost,
2053
+ * - critical paths (initial corruption-archived fresh-start),
2054
+ * - tests.
2055
+ *
2056
+ * Internally serialises against any in-flight write — only one rename
2057
+ * happens at a time per store.
2058
+ */
2059
+ async flush() {
2060
+ if (this.saveTimer) {
2061
+ clearTimeout(this.saveTimer);
2062
+ this.saveTimer = null;
2063
+ }
2064
+ const snapshot = JSON.stringify(this.state, null, 2);
2065
+ const writeNow = () => writeFileAtomic(this.statePath, snapshot);
2066
+ const next = this.writeChain.then(writeNow, writeNow);
2067
+ this.writeChain = next.catch(() => { });
2068
+ await next;
2069
+ }
2070
+ }
2071
+ exports.StateStore = StateStore;
2072
+ // ─── singleton wiring ────────────────────────────────────────────────
2073
+ let singleton = null;
2074
+ let singletonInFlight = null;
2075
+ /**
2076
+ * @description Resolve the default `DATA_DIR` for the running bot.
2077
+ *
2078
+ * Priority:
2079
+ * 1. `process.env.DATA_DIR` (set by the operator — required for the
2080
+ * two-instance setup, plan §10.7).
2081
+ * 2. `~/.telegramCode` — single-instance default.
2082
+ *
2083
+ * Two bots on the same host MUST set distinct `DATA_DIR` values, otherwise
2084
+ * they'd silently share `state.json` and corrupt each other. Plan §16.2
2085
+ * adds a startup `.lock` check; that lives in `bot.ts` (Этап 3), not here.
2086
+ */
2087
+ function resolveDataDir() {
2088
+ return process.env.DATA_DIR || path.join(os.homedir(), '.telegramCode');
2089
+ }
2090
+ /**
2091
+ * @description Create-or-fetch the singleton store. The first call performs
2092
+ * `init()` and persists archival/migration side-effects; subsequent calls
2093
+ * just return the cached instance.
2094
+ *
2095
+ * The in-flight promise is cached to keep concurrent first-callers from
2096
+ * each constructing a separate `StateStore` (two stores → two competing
2097
+ * write chains → corrupted JSON). Production triggers only one call from
2098
+ * `startBot()`, but the cost of the guard is trivial.
2099
+ *
2100
+ * Tests should instantiate `new StateStore(...)` directly instead.
2101
+ */
2102
+ async function getStateStore() {
2103
+ if (singleton)
2104
+ return singleton;
2105
+ if (singletonInFlight)
2106
+ return singletonInFlight;
2107
+ singletonInFlight = (async () => {
2108
+ const store = new StateStore(resolveDataDir());
2109
+ await store.init();
2110
+ singleton = store;
2111
+ return store;
2112
+ })();
2113
+ try {
2114
+ return await singletonInFlight;
2115
+ }
2116
+ finally {
2117
+ singletonInFlight = null;
2118
+ }
2119
+ }
2120
+ /**
2121
+ * @description Reset the singleton — only for tests. Production code should
2122
+ * never call this; doing so would leak in-flight write promises.
2123
+ */
2124
+ function _resetStateStoreSingletonForTests() {
2125
+ singleton = null;
2126
+ singletonInFlight = null;
2127
+ }
2128
+ //# sourceMappingURL=state.js.map