charness 3.6.1 → 3.7.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 (578) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/DEVELOPMENT.md +31 -5
  3. package/MIGRATION_RENAME_TO_CHARNESS.md +3 -2
  4. package/README.md +84 -782
  5. package/SETUP.md +37 -10
  6. package/agent-plugin/.claude-plugin/plugin.json +7 -0
  7. package/agent-plugin/README.md +35 -0
  8. package/agent-plugin/skills/charness-delegate-claude/SKILL.md +85 -0
  9. package/agent-plugin/skills/charness-delegate-claude/summarize-events.py +90 -0
  10. package/agent-plugin/skills/charness-delegate-opencode/SKILL.md +68 -0
  11. package/agent-plugin/skills/charness-manager/SKILL.md +152 -0
  12. package/dist/adapters/adapterNames.d.ts +6 -0
  13. package/dist/adapters/adapterNames.d.ts.map +1 -1
  14. package/dist/adapters/adapterNames.js +8 -2
  15. package/dist/adapters/adapterNames.js.map +1 -1
  16. package/dist/adapters/claudeCliAdapter.d.ts +8 -0
  17. package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
  18. package/dist/adapters/claudeCliAdapter.js +42 -5
  19. package/dist/adapters/claudeCliAdapter.js.map +1 -1
  20. package/dist/adapters/claudeJsonStreamAdapter.d.ts +10 -9
  21. package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
  22. package/dist/adapters/claudeJsonStreamAdapter.js +53 -55
  23. package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
  24. package/dist/adapters/createAdapter.d.ts +8 -0
  25. package/dist/adapters/createAdapter.d.ts.map +1 -1
  26. package/dist/adapters/createAdapter.js +14 -2
  27. package/dist/adapters/createAdapter.js.map +1 -1
  28. package/dist/adapters/openCodeAdapter.d.ts +43 -6
  29. package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
  30. package/dist/adapters/openCodeAdapter.js +95 -6
  31. package/dist/adapters/openCodeAdapter.js.map +1 -1
  32. package/dist/agentLogin/agentLogin.d.ts +1 -1
  33. package/dist/agentLogin/agentLogin.d.ts.map +1 -1
  34. package/dist/apiRetryKick.d.ts +4 -3
  35. package/dist/apiRetryKick.d.ts.map +1 -1
  36. package/dist/apiRetryKick.js +16 -9
  37. package/dist/apiRetryKick.js.map +1 -1
  38. package/dist/bot.d.ts.map +1 -1
  39. package/dist/bot.js +1313 -381
  40. package/dist/bot.js.map +1 -1
  41. package/dist/cli/claudeProfiles.d.ts +11 -6
  42. package/dist/cli/claudeProfiles.d.ts.map +1 -1
  43. package/dist/cli/claudeProfiles.js +50 -22
  44. package/dist/cli/claudeProfiles.js.map +1 -1
  45. package/dist/cli/hot.d.ts +5 -0
  46. package/dist/cli/hot.d.ts.map +1 -1
  47. package/dist/cli/hot.js +71 -25
  48. package/dist/cli/hot.js.map +1 -1
  49. package/dist/cli/tscWatchGuard.d.ts +95 -0
  50. package/dist/cli/tscWatchGuard.d.ts.map +1 -0
  51. package/dist/cli/tscWatchGuard.js +295 -0
  52. package/dist/cli/tscWatchGuard.js.map +1 -0
  53. package/dist/cli.js +12 -1
  54. package/dist/cli.js.map +1 -1
  55. package/dist/connectors/jira/answerSink.d.ts +10 -0
  56. package/dist/connectors/jira/answerSink.d.ts.map +1 -1
  57. package/dist/connectors/jira/answerSink.js +13 -0
  58. package/dist/connectors/jira/answerSink.js.map +1 -1
  59. package/dist/connectors/jira/config.d.ts +2 -0
  60. package/dist/connectors/jira/config.d.ts.map +1 -1
  61. package/dist/connectors/jira/config.js +9 -1
  62. package/dist/connectors/jira/config.js.map +1 -1
  63. package/dist/connectors/jira/connector.d.ts +11 -1
  64. package/dist/connectors/jira/connector.d.ts.map +1 -1
  65. package/dist/connectors/jira/connector.js +7 -0
  66. package/dist/connectors/jira/connector.js.map +1 -1
  67. package/dist/connectors/jira/inbound.d.ts +27 -4
  68. package/dist/connectors/jira/inbound.d.ts.map +1 -1
  69. package/dist/connectors/jira/inbound.js +69 -8
  70. package/dist/connectors/jira/inbound.js.map +1 -1
  71. package/dist/connectors/jira/issueBlocks.d.ts +5 -0
  72. package/dist/connectors/jira/issueBlocks.d.ts.map +1 -1
  73. package/dist/connectors/jira/issueBlocks.js +11 -0
  74. package/dist/connectors/jira/issueBlocks.js.map +1 -1
  75. package/dist/connectors/telegram/bindPicker.d.ts +5 -0
  76. package/dist/connectors/telegram/bindPicker.d.ts.map +1 -1
  77. package/dist/connectors/telegram/bindPicker.js +18 -1
  78. package/dist/connectors/telegram/bindPicker.js.map +1 -1
  79. package/dist/connectors/telegram/bindPickerClaims.d.ts +46 -0
  80. package/dist/connectors/telegram/bindPickerClaims.d.ts.map +1 -0
  81. package/dist/connectors/telegram/bindPickerClaims.js +83 -0
  82. package/dist/connectors/telegram/bindPickerClaims.js.map +1 -0
  83. package/dist/connectors/telegram/bindSweep.d.ts +32 -0
  84. package/dist/connectors/telegram/bindSweep.d.ts.map +1 -0
  85. package/dist/connectors/telegram/bindSweep.js +47 -0
  86. package/dist/connectors/telegram/bindSweep.js.map +1 -0
  87. package/dist/connectors/telegram/bindingWelcome.d.ts +23 -0
  88. package/dist/connectors/telegram/bindingWelcome.d.ts.map +1 -0
  89. package/dist/connectors/telegram/bindingWelcome.js +26 -0
  90. package/dist/connectors/telegram/bindingWelcome.js.map +1 -0
  91. package/dist/connectors/telegram/botApiConnection.d.ts +74 -0
  92. package/dist/connectors/telegram/botApiConnection.d.ts.map +1 -0
  93. package/dist/connectors/telegram/botApiConnection.js +201 -0
  94. package/dist/connectors/telegram/botApiConnection.js.map +1 -0
  95. package/dist/connectors/telegram/commands/botCore.d.ts +8 -2
  96. package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -1
  97. package/dist/connectors/telegram/commands/botSkills.d.ts +21 -0
  98. package/dist/connectors/telegram/commands/botSkills.d.ts.map +1 -0
  99. package/dist/connectors/telegram/commands/botSkills.js +133 -0
  100. package/dist/connectors/telegram/commands/botSkills.js.map +1 -0
  101. package/dist/connectors/telegram/commands/callbackMessage.d.ts +5 -0
  102. package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
  103. package/dist/connectors/telegram/commands/callbackMessage.js +9 -0
  104. package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
  105. package/dist/connectors/telegram/commands/compaction.d.ts +65 -3
  106. package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -1
  107. package/dist/connectors/telegram/commands/compaction.js +512 -94
  108. package/dist/connectors/telegram/commands/compaction.js.map +1 -1
  109. package/dist/connectors/telegram/commands/displayModes.d.ts +1 -1
  110. package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
  111. package/dist/connectors/telegram/commands/displayModes.js +6 -18
  112. package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
  113. package/dist/connectors/telegram/commands/fallbackChain.js +5 -5
  114. package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
  115. package/dist/connectors/telegram/commands/reminders.d.ts +1 -1
  116. package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -1
  117. package/dist/connectors/telegram/commands/reminders.js +4 -15
  118. package/dist/connectors/telegram/commands/reminders.js.map +1 -1
  119. package/dist/connectors/telegram/commands/selfUpdate.d.ts.map +1 -1
  120. package/dist/connectors/telegram/commands/selfUpdate.js +1 -11
  121. package/dist/connectors/telegram/commands/selfUpdate.js.map +1 -1
  122. package/dist/connectors/telegram/commands/taskAgents.d.ts +22 -0
  123. package/dist/connectors/telegram/commands/taskAgents.d.ts.map +1 -0
  124. package/dist/connectors/telegram/commands/taskAgents.js +104 -0
  125. package/dist/connectors/telegram/commands/taskAgents.js.map +1 -0
  126. package/dist/connectors/telegram/commands/todo.d.ts +82 -0
  127. package/dist/connectors/telegram/commands/todo.d.ts.map +1 -0
  128. package/dist/connectors/telegram/commands/todo.js +384 -0
  129. package/dist/connectors/telegram/commands/todo.js.map +1 -0
  130. package/dist/connectors/telegram/commands/topicCommand.d.ts +6 -2
  131. package/dist/connectors/telegram/commands/topicCommand.d.ts.map +1 -1
  132. package/dist/connectors/telegram/commands/topicCommand.js +6 -9
  133. package/dist/connectors/telegram/commands/topicCommand.js.map +1 -1
  134. package/dist/connectors/telegram/openBindMessages.d.ts +45 -0
  135. package/dist/connectors/telegram/openBindMessages.d.ts.map +1 -0
  136. package/dist/connectors/telegram/openBindMessages.js +67 -0
  137. package/dist/connectors/telegram/openBindMessages.js.map +1 -0
  138. package/dist/connectors/telegram/outputFlushPlan.d.ts +49 -0
  139. package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -1
  140. package/dist/connectors/telegram/outputFlushPlan.js +46 -0
  141. package/dist/connectors/telegram/outputFlushPlan.js.map +1 -1
  142. package/dist/connectors/telegram/pendingTopicName.d.ts +26 -0
  143. package/dist/connectors/telegram/pendingTopicName.d.ts.map +1 -0
  144. package/dist/connectors/telegram/pendingTopicName.js +27 -0
  145. package/dist/connectors/telegram/pendingTopicName.js.map +1 -0
  146. package/dist/connectors/test/connector.d.ts.map +1 -1
  147. package/dist/connectors/test/connector.js +6 -1
  148. package/dist/connectors/test/connector.js.map +1 -1
  149. package/dist/i18n/de.d.ts.map +1 -1
  150. package/dist/i18n/de.js +90 -7
  151. package/dist/i18n/de.js.map +1 -1
  152. package/dist/i18n/en.d.ts.map +1 -1
  153. package/dist/i18n/en.js +90 -7
  154. package/dist/i18n/en.js.map +1 -1
  155. package/dist/i18n/es.d.ts.map +1 -1
  156. package/dist/i18n/es.js +90 -7
  157. package/dist/i18n/es.js.map +1 -1
  158. package/dist/i18n/fr.d.ts.map +1 -1
  159. package/dist/i18n/fr.js +90 -7
  160. package/dist/i18n/fr.js.map +1 -1
  161. package/dist/i18n/hi.d.ts.map +1 -1
  162. package/dist/i18n/hi.js +90 -7
  163. package/dist/i18n/hi.js.map +1 -1
  164. package/dist/i18n/ja.d.ts.map +1 -1
  165. package/dist/i18n/ja.js +90 -7
  166. package/dist/i18n/ja.js.map +1 -1
  167. package/dist/i18n/ka.d.ts.map +1 -1
  168. package/dist/i18n/ka.js +90 -7
  169. package/dist/i18n/ka.js.map +1 -1
  170. package/dist/i18n/pt.d.ts.map +1 -1
  171. package/dist/i18n/pt.js +90 -7
  172. package/dist/i18n/pt.js.map +1 -1
  173. package/dist/i18n/ru.d.ts.map +1 -1
  174. package/dist/i18n/ru.js +90 -7
  175. package/dist/i18n/ru.js.map +1 -1
  176. package/dist/i18n/uk.d.ts.map +1 -1
  177. package/dist/i18n/uk.js +90 -7
  178. package/dist/i18n/uk.js.map +1 -1
  179. package/dist/i18n/uz.d.ts.map +1 -1
  180. package/dist/i18n/uz.js +90 -7
  181. package/dist/i18n/uz.js.map +1 -1
  182. package/dist/i18n/zh.d.ts.map +1 -1
  183. package/dist/i18n/zh.js +90 -7
  184. package/dist/i18n/zh.js.map +1 -1
  185. package/dist/i18n.d.ts +1 -1
  186. package/dist/i18n.js +1 -1
  187. package/dist/installManager.d.ts +13 -0
  188. package/dist/installManager.d.ts.map +1 -1
  189. package/dist/installManager.js +38 -4
  190. package/dist/installManager.js.map +1 -1
  191. package/dist/instanceSettings.d.ts +40 -1
  192. package/dist/instanceSettings.d.ts.map +1 -1
  193. package/dist/instanceSettings.js +105 -26
  194. package/dist/instanceSettings.js.map +1 -1
  195. package/dist/openCodeQuestionFlow.d.ts +30 -0
  196. package/dist/openCodeQuestionFlow.d.ts.map +1 -1
  197. package/dist/openCodeQuestionFlow.js +43 -0
  198. package/dist/openCodeQuestionFlow.js.map +1 -1
  199. package/dist/postToSession.d.ts +20 -5
  200. package/dist/postToSession.d.ts.map +1 -1
  201. package/dist/postToSession.js +38 -7
  202. package/dist/postToSession.js.map +1 -1
  203. package/dist/remoteRun/cliArgs.d.ts +73 -0
  204. package/dist/remoteRun/cliArgs.d.ts.map +1 -0
  205. package/dist/remoteRun/cliArgs.js +197 -0
  206. package/dist/remoteRun/cliArgs.js.map +1 -0
  207. package/dist/remoteRun/commands.d.ts +56 -0
  208. package/dist/remoteRun/commands.d.ts.map +1 -0
  209. package/dist/remoteRun/commands.js +306 -0
  210. package/dist/remoteRun/commands.js.map +1 -0
  211. package/dist/remoteRun/depsPlan.d.ts +16 -0
  212. package/dist/remoteRun/depsPlan.d.ts.map +1 -0
  213. package/dist/remoteRun/depsPlan.js +82 -0
  214. package/dist/remoteRun/depsPlan.js.map +1 -0
  215. package/dist/remoteRun/exitCodes.d.ts +34 -0
  216. package/dist/remoteRun/exitCodes.d.ts.map +1 -0
  217. package/dist/remoteRun/exitCodes.js +38 -0
  218. package/dist/remoteRun/exitCodes.js.map +1 -0
  219. package/dist/remoteRun/foreground.d.ts +29 -0
  220. package/dist/remoteRun/foreground.d.ts.map +1 -0
  221. package/dist/remoteRun/foreground.js +159 -0
  222. package/dist/remoteRun/foreground.js.map +1 -0
  223. package/dist/remoteRun/interrupts.d.ts +17 -0
  224. package/dist/remoteRun/interrupts.d.ts.map +1 -0
  225. package/dist/remoteRun/interrupts.js +41 -0
  226. package/dist/remoteRun/interrupts.js.map +1 -0
  227. package/dist/remoteRun/jobFiles.d.ts +31 -0
  228. package/dist/remoteRun/jobFiles.d.ts.map +1 -0
  229. package/dist/remoteRun/jobFiles.js +84 -0
  230. package/dist/remoteRun/jobFiles.js.map +1 -0
  231. package/dist/remoteRun/jobFormat.d.ts +16 -0
  232. package/dist/remoteRun/jobFormat.d.ts.map +1 -0
  233. package/dist/remoteRun/jobFormat.js +130 -0
  234. package/dist/remoteRun/jobFormat.js.map +1 -0
  235. package/dist/remoteRun/jobId.d.ts +15 -0
  236. package/dist/remoteRun/jobId.d.ts.map +1 -0
  237. package/dist/remoteRun/jobId.js +85 -0
  238. package/dist/remoteRun/jobId.js.map +1 -0
  239. package/dist/remoteRun/jobMeta.d.ts +18 -0
  240. package/dist/remoteRun/jobMeta.d.ts.map +1 -0
  241. package/dist/remoteRun/jobMeta.js +33 -0
  242. package/dist/remoteRun/jobMeta.js.map +1 -0
  243. package/dist/remoteRun/jobQuery.d.ts +12 -0
  244. package/dist/remoteRun/jobQuery.d.ts.map +1 -0
  245. package/dist/remoteRun/jobQuery.js +22 -0
  246. package/dist/remoteRun/jobQuery.js.map +1 -0
  247. package/dist/remoteRun/jobRecords.d.ts +22 -0
  248. package/dist/remoteRun/jobRecords.d.ts.map +1 -0
  249. package/dist/remoteRun/jobRecords.js +145 -0
  250. package/dist/remoteRun/jobRecords.js.map +1 -0
  251. package/dist/remoteRun/jobState.d.ts +62 -0
  252. package/dist/remoteRun/jobState.d.ts.map +1 -0
  253. package/dist/remoteRun/jobState.js +132 -0
  254. package/dist/remoteRun/jobState.js.map +1 -0
  255. package/dist/remoteRun/leaseTiming.d.ts +18 -0
  256. package/dist/remoteRun/leaseTiming.d.ts.map +1 -0
  257. package/dist/remoteRun/leaseTiming.js +43 -0
  258. package/dist/remoteRun/leaseTiming.js.map +1 -0
  259. package/dist/remoteRun/output.d.ts +35 -0
  260. package/dist/remoteRun/output.d.ts.map +1 -0
  261. package/dist/remoteRun/output.js +82 -0
  262. package/dist/remoteRun/output.js.map +1 -0
  263. package/dist/remoteRun/pathPatterns.d.ts +33 -0
  264. package/dist/remoteRun/pathPatterns.d.ts.map +1 -0
  265. package/dist/remoteRun/pathPatterns.js +115 -0
  266. package/dist/remoteRun/pathPatterns.js.map +1 -0
  267. package/dist/remoteRun/probe.d.ts +56 -0
  268. package/dist/remoteRun/probe.d.ts.map +1 -0
  269. package/dist/remoteRun/probe.js +123 -0
  270. package/dist/remoteRun/probe.js.map +1 -0
  271. package/dist/remoteRun/project.d.ts +26 -0
  272. package/dist/remoteRun/project.d.ts.map +1 -0
  273. package/dist/remoteRun/project.js +159 -0
  274. package/dist/remoteRun/project.js.map +1 -0
  275. package/dist/remoteRun/projectConfig.d.ts +38 -0
  276. package/dist/remoteRun/projectConfig.d.ts.map +1 -0
  277. package/dist/remoteRun/projectConfig.js +87 -0
  278. package/dist/remoteRun/projectConfig.js.map +1 -0
  279. package/dist/remoteRun/remoteRunCli.d.ts +15 -0
  280. package/dist/remoteRun/remoteRunCli.d.ts.map +1 -0
  281. package/dist/remoteRun/remoteRunCli.js +144 -0
  282. package/dist/remoteRun/remoteRunCli.js.map +1 -0
  283. package/dist/remoteRun/remoteRunError.d.ts +14 -0
  284. package/dist/remoteRun/remoteRunError.d.ts.map +1 -0
  285. package/dist/remoteRun/remoteRunError.js +28 -0
  286. package/dist/remoteRun/remoteRunError.js.map +1 -0
  287. package/dist/remoteRun/rsync.d.ts +17 -0
  288. package/dist/remoteRun/rsync.d.ts.map +1 -0
  289. package/dist/remoteRun/rsync.js +39 -0
  290. package/dist/remoteRun/rsync.js.map +1 -0
  291. package/dist/remoteRun/runnerClient.d.ts +50 -0
  292. package/dist/remoteRun/runnerClient.d.ts.map +1 -0
  293. package/dist/remoteRun/runnerClient.js +139 -0
  294. package/dist/remoteRun/runnerClient.js.map +1 -0
  295. package/dist/remoteRun/runners.d.ts +57 -0
  296. package/dist/remoteRun/runners.d.ts.map +1 -0
  297. package/dist/remoteRun/runners.js +181 -0
  298. package/dist/remoteRun/runners.js.map +1 -0
  299. package/dist/remoteRun/runnersConfig.d.ts +44 -0
  300. package/dist/remoteRun/runnersConfig.d.ts.map +1 -0
  301. package/dist/remoteRun/runnersConfig.js +107 -0
  302. package/dist/remoteRun/runnersConfig.js.map +1 -0
  303. package/dist/remoteRun/safeCopy.d.ts +31 -0
  304. package/dist/remoteRun/safeCopy.d.ts.map +1 -0
  305. package/dist/remoteRun/safeCopy.js +137 -0
  306. package/dist/remoteRun/safeCopy.js.map +1 -0
  307. package/dist/remoteRun/sendPlan.d.ts +67 -0
  308. package/dist/remoteRun/sendPlan.d.ts.map +1 -0
  309. package/dist/remoteRun/sendPlan.js +139 -0
  310. package/dist/remoteRun/sendPlan.js.map +1 -0
  311. package/dist/remoteRun/ssh.d.ts +62 -0
  312. package/dist/remoteRun/ssh.d.ts.map +1 -0
  313. package/dist/remoteRun/ssh.js +202 -0
  314. package/dist/remoteRun/ssh.js.map +1 -0
  315. package/dist/remoteRun/submit.d.ts +29 -0
  316. package/dist/remoteRun/submit.d.ts.map +1 -0
  317. package/dist/remoteRun/submit.js +142 -0
  318. package/dist/remoteRun/submit.js.map +1 -0
  319. package/dist/requests/answerRequest.d.ts +5 -0
  320. package/dist/requests/answerRequest.d.ts.map +1 -1
  321. package/dist/requests/answerRequest.js +16 -1
  322. package/dist/requests/answerRequest.js.map +1 -1
  323. package/dist/requests/requestLedger.d.ts +13 -0
  324. package/dist/requests/requestLedger.d.ts.map +1 -1
  325. package/dist/requests/requestLedger.js +9 -0
  326. package/dist/requests/requestLedger.js.map +1 -1
  327. package/dist/requests/topicRequestSettle.d.ts +30 -0
  328. package/dist/requests/topicRequestSettle.d.ts.map +1 -0
  329. package/dist/requests/topicRequestSettle.js +23 -0
  330. package/dist/requests/topicRequestSettle.js.map +1 -0
  331. package/dist/requests/wakeUpEngine.d.ts +4 -0
  332. package/dist/requests/wakeUpEngine.d.ts.map +1 -1
  333. package/dist/requests/wakeUpEngine.js +5 -1
  334. package/dist/requests/wakeUpEngine.js.map +1 -1
  335. package/dist/scheduler/delivery.d.ts +2 -0
  336. package/dist/scheduler/delivery.d.ts.map +1 -1
  337. package/dist/scheduler/delivery.js +8 -4
  338. package/dist/scheduler/delivery.js.map +1 -1
  339. package/dist/scheduler/mcpSurface.d.ts +59 -10
  340. package/dist/scheduler/mcpSurface.d.ts.map +1 -1
  341. package/dist/scheduler/mcpSurface.js +276 -79
  342. package/dist/scheduler/mcpSurface.js.map +1 -1
  343. package/dist/scheduler/recurrence.d.ts +5 -0
  344. package/dist/scheduler/recurrence.d.ts.map +1 -1
  345. package/dist/scheduler/recurrence.js +12 -2
  346. package/dist/scheduler/recurrence.js.map +1 -1
  347. package/dist/scheduler/strictToolInput.d.ts +45 -0
  348. package/dist/scheduler/strictToolInput.d.ts.map +1 -0
  349. package/dist/scheduler/strictToolInput.js +155 -0
  350. package/dist/scheduler/strictToolInput.js.map +1 -0
  351. package/dist/scheduler/types.d.ts +5 -0
  352. package/dist/scheduler/types.d.ts.map +1 -1
  353. package/dist/scheduler/workerWatch.d.ts +23 -0
  354. package/dist/scheduler/workerWatch.d.ts.map +1 -0
  355. package/dist/scheduler/workerWatch.js +26 -0
  356. package/dist/scheduler/workerWatch.js.map +1 -0
  357. package/dist/selfUpdate/emittedSource.d.ts +11 -0
  358. package/dist/selfUpdate/emittedSource.d.ts.map +1 -0
  359. package/dist/selfUpdate/emittedSource.js +24 -0
  360. package/dist/selfUpdate/emittedSource.js.map +1 -0
  361. package/dist/selfUpdate/gitSource.d.ts +0 -6
  362. package/dist/selfUpdate/gitSource.d.ts.map +1 -1
  363. package/dist/selfUpdate/gitSource.js +2 -18
  364. package/dist/selfUpdate/gitSource.js.map +1 -1
  365. package/dist/selfUpdate/updatePending.d.ts +1 -1
  366. package/dist/sendErrorClassifier.d.ts +10 -0
  367. package/dist/sendErrorClassifier.d.ts.map +1 -1
  368. package/dist/sendErrorClassifier.js +22 -0
  369. package/dist/sendErrorClassifier.js.map +1 -1
  370. package/dist/state.d.ts +78 -7
  371. package/dist/state.d.ts.map +1 -1
  372. package/dist/state.js +125 -8
  373. package/dist/state.js.map +1 -1
  374. package/dist/stateFile.d.ts +9 -0
  375. package/dist/stateFile.d.ts.map +1 -0
  376. package/dist/stateFile.js +57 -0
  377. package/dist/stateFile.js.map +1 -0
  378. package/dist/stt/sttInstall.d.ts +7 -3
  379. package/dist/stt/sttInstall.d.ts.map +1 -1
  380. package/dist/stt/sttInstall.js +8 -4
  381. package/dist/stt/sttInstall.js.map +1 -1
  382. package/dist/stt/sttSettings.d.ts +2 -1
  383. package/dist/stt/sttSettings.d.ts.map +1 -1
  384. package/dist/stt/sttSettings.js +3 -2
  385. package/dist/stt/sttSettings.js.map +1 -1
  386. package/dist/stt/transcribeChain.js +2 -0
  387. package/dist/stt/transcribeChain.js.map +1 -1
  388. package/dist/testTimings.d.ts +13 -3
  389. package/dist/testTimings.d.ts.map +1 -1
  390. package/dist/testTimings.js +26 -2
  391. package/dist/testTimings.js.map +1 -1
  392. package/dist/todos/board.d.ts +83 -0
  393. package/dist/todos/board.d.ts.map +1 -0
  394. package/dist/todos/board.js +227 -0
  395. package/dist/todos/board.js.map +1 -0
  396. package/dist/todos/format.d.ts +33 -0
  397. package/dist/todos/format.d.ts.map +1 -0
  398. package/dist/todos/format.js +90 -0
  399. package/dist/todos/format.js.map +1 -0
  400. package/dist/todos/merge.d.ts +46 -0
  401. package/dist/todos/merge.d.ts.map +1 -0
  402. package/dist/todos/merge.js +137 -0
  403. package/dist/todos/merge.js.map +1 -0
  404. package/dist/todos/midTurn.d.ts +75 -0
  405. package/dist/todos/midTurn.d.ts.map +1 -0
  406. package/dist/todos/midTurn.js +214 -0
  407. package/dist/todos/midTurn.js.map +1 -0
  408. package/dist/todos/nag.d.ts +85 -0
  409. package/dist/todos/nag.d.ts.map +1 -0
  410. package/dist/todos/nag.js +103 -0
  411. package/dist/todos/nag.js.map +1 -0
  412. package/dist/todos/order.d.ts +25 -0
  413. package/dist/todos/order.d.ts.map +1 -0
  414. package/dist/todos/order.js +61 -0
  415. package/dist/todos/order.js.map +1 -0
  416. package/dist/todos/projects.d.ts +102 -0
  417. package/dist/todos/projects.d.ts.map +1 -0
  418. package/dist/todos/projects.js +342 -0
  419. package/dist/todos/projects.js.map +1 -0
  420. package/dist/todos/registry.d.ts +64 -0
  421. package/dist/todos/registry.d.ts.map +1 -0
  422. package/dist/todos/registry.js +116 -0
  423. package/dist/todos/registry.js.map +1 -0
  424. package/dist/todos/reminderEngine.d.ts +78 -0
  425. package/dist/todos/reminderEngine.d.ts.map +1 -0
  426. package/dist/todos/reminderEngine.js +162 -0
  427. package/dist/todos/reminderEngine.js.map +1 -0
  428. package/dist/todos/store.d.ts +34 -0
  429. package/dist/todos/store.d.ts.map +1 -0
  430. package/dist/todos/store.js +208 -0
  431. package/dist/todos/store.js.map +1 -0
  432. package/dist/todos/tools.d.ts +26 -0
  433. package/dist/todos/tools.d.ts.map +1 -0
  434. package/dist/todos/tools.js +25 -0
  435. package/dist/todos/tools.js.map +1 -0
  436. package/dist/todos/types.d.ts +85 -0
  437. package/dist/todos/types.d.ts.map +1 -0
  438. package/dist/todos/types.js +30 -0
  439. package/dist/todos/types.js.map +1 -0
  440. package/dist/trackers/taskAgentLimit.d.ts +58 -0
  441. package/dist/trackers/taskAgentLimit.d.ts.map +1 -0
  442. package/dist/trackers/taskAgentLimit.js +62 -0
  443. package/dist/trackers/taskAgentLimit.js.map +1 -0
  444. package/dist/trackers/taskAgentSlots.d.ts +109 -0
  445. package/dist/trackers/taskAgentSlots.d.ts.map +1 -0
  446. package/dist/trackers/taskAgentSlots.js +227 -0
  447. package/dist/trackers/taskAgentSlots.js.map +1 -0
  448. package/dist/trackers/types.d.ts +14 -0
  449. package/dist/trackers/types.d.ts.map +1 -0
  450. package/dist/trackers/types.js +3 -0
  451. package/dist/trackers/types.js.map +1 -0
  452. package/dist/types.d.ts +80 -4
  453. package/dist/types.d.ts.map +1 -1
  454. package/dist/utils/adoptReplayGate.d.ts +16 -0
  455. package/dist/utils/adoptReplayGate.d.ts.map +1 -0
  456. package/dist/utils/adoptReplayGate.js +35 -0
  457. package/dist/utils/adoptReplayGate.js.map +1 -0
  458. package/dist/utils/agentPlugin.d.ts +113 -0
  459. package/dist/utils/agentPlugin.d.ts.map +1 -0
  460. package/dist/utils/agentPlugin.js +192 -0
  461. package/dist/utils/agentPlugin.js.map +1 -0
  462. package/dist/utils/autoContinueOnLimit.d.ts +7 -4
  463. package/dist/utils/autoContinueOnLimit.d.ts.map +1 -1
  464. package/dist/utils/autoContinueOnLimit.js +8 -5
  465. package/dist/utils/autoContinueOnLimit.js.map +1 -1
  466. package/dist/utils/botSkills.d.ts +62 -0
  467. package/dist/utils/botSkills.d.ts.map +1 -0
  468. package/dist/utils/botSkills.js +53 -0
  469. package/dist/utils/botSkills.js.map +1 -0
  470. package/dist/utils/botSkillsSetting.d.ts +7 -0
  471. package/dist/utils/botSkillsSetting.d.ts.map +1 -0
  472. package/dist/utils/botSkillsSetting.js +9 -0
  473. package/dist/utils/botSkillsSetting.js.map +1 -0
  474. package/dist/utils/claudeCodeVersion.d.ts +7 -0
  475. package/dist/utils/claudeCodeVersion.d.ts.map +1 -1
  476. package/dist/utils/claudeCodeVersion.js +14 -1
  477. package/dist/utils/claudeCodeVersion.js.map +1 -1
  478. package/dist/utils/claudeCompactHook.d.ts +33 -15
  479. package/dist/utils/claudeCompactHook.d.ts.map +1 -1
  480. package/dist/utils/claudeCompactHook.js +36 -17
  481. package/dist/utils/claudeCompactHook.js.map +1 -1
  482. package/dist/utils/claudeCompactSummary.d.ts +15 -0
  483. package/dist/utils/claudeCompactSummary.d.ts.map +1 -0
  484. package/dist/utils/claudeCompactSummary.js +96 -0
  485. package/dist/utils/claudeCompactSummary.js.map +1 -0
  486. package/dist/utils/claudeLimitSwitch.d.ts +11 -9
  487. package/dist/utils/claudeLimitSwitch.d.ts.map +1 -1
  488. package/dist/utils/claudeLimitSwitch.js +18 -16
  489. package/dist/utils/claudeLimitSwitch.js.map +1 -1
  490. package/dist/utils/claudeProfiles.d.ts +5 -0
  491. package/dist/utils/claudeProfiles.d.ts.map +1 -1
  492. package/dist/utils/claudeProfiles.js +14 -0
  493. package/dist/utils/claudeProfiles.js.map +1 -1
  494. package/dist/utils/claudeSessionTitle.d.ts +2 -1
  495. package/dist/utils/claudeSessionTitle.d.ts.map +1 -1
  496. package/dist/utils/claudeSessionTitle.js +14 -14
  497. package/dist/utils/claudeSessionTitle.js.map +1 -1
  498. package/dist/utils/claudeUsage.d.ts +95 -15
  499. package/dist/utils/claudeUsage.d.ts.map +1 -1
  500. package/dist/utils/claudeUsage.js +241 -41
  501. package/dist/utils/claudeUsage.js.map +1 -1
  502. package/dist/utils/claudeUsageRefresher.d.ts +10 -0
  503. package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
  504. package/dist/utils/claudeUsageRefresher.js +11 -0
  505. package/dist/utils/claudeUsageRefresher.js.map +1 -1
  506. package/dist/utils/claudeUserEntry.d.ts +50 -0
  507. package/dist/utils/claudeUserEntry.d.ts.map +1 -0
  508. package/dist/utils/claudeUserEntry.js +120 -0
  509. package/dist/utils/claudeUserEntry.js.map +1 -0
  510. package/dist/utils/clearThreadOutputQueues.d.ts +4 -3
  511. package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -1
  512. package/dist/utils/clearThreadOutputQueues.js +1 -1
  513. package/dist/utils/clearThreadOutputQueues.js.map +1 -1
  514. package/dist/utils/compactOnIdle.d.ts +33 -13
  515. package/dist/utils/compactOnIdle.d.ts.map +1 -1
  516. package/dist/utils/compactOnIdle.js +35 -15
  517. package/dist/utils/compactOnIdle.js.map +1 -1
  518. package/dist/utils/fallbackBridge.d.ts +5 -0
  519. package/dist/utils/fallbackBridge.d.ts.map +1 -1
  520. package/dist/utils/fallbackBridge.js +19 -3
  521. package/dist/utils/fallbackBridge.js.map +1 -1
  522. package/dist/utils/fallbackChain.d.ts +9 -3
  523. package/dist/utils/fallbackChain.d.ts.map +1 -1
  524. package/dist/utils/fallbackChain.js +21 -3
  525. package/dist/utils/fallbackChain.js.map +1 -1
  526. package/dist/utils/fileSendPlan.d.ts +6 -0
  527. package/dist/utils/fileSendPlan.d.ts.map +1 -1
  528. package/dist/utils/fileSendPlan.js +11 -0
  529. package/dist/utils/fileSendPlan.js.map +1 -1
  530. package/dist/utils/fileSendService.d.ts +7 -1
  531. package/dist/utils/fileSendService.d.ts.map +1 -1
  532. package/dist/utils/fileSendService.js +6 -2
  533. package/dist/utils/fileSendService.js.map +1 -1
  534. package/dist/utils/groupFinalizePlan.d.ts +8 -10
  535. package/dist/utils/groupFinalizePlan.d.ts.map +1 -1
  536. package/dist/utils/groupFinalizePlan.js +8 -12
  537. package/dist/utils/groupFinalizePlan.js.map +1 -1
  538. package/dist/utils/jsonStreamHost.d.ts +13 -0
  539. package/dist/utils/jsonStreamHost.d.ts.map +1 -1
  540. package/dist/utils/jsonStreamHost.js +13 -0
  541. package/dist/utils/jsonStreamHost.js.map +1 -1
  542. package/dist/utils/messageSendService.d.ts +5 -3
  543. package/dist/utils/messageSendService.d.ts.map +1 -1
  544. package/dist/utils/messageSendService.js +12 -5
  545. package/dist/utils/messageSendService.js.map +1 -1
  546. package/dist/utils/openCodeCompactPlugin.d.ts +39 -14
  547. package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -1
  548. package/dist/utils/openCodeCompactPlugin.js +106 -38
  549. package/dist/utils/openCodeCompactPlugin.js.map +1 -1
  550. package/dist/utils/promptDelivery.d.ts +29 -12
  551. package/dist/utils/promptDelivery.d.ts.map +1 -1
  552. package/dist/utils/promptDelivery.js +13 -5
  553. package/dist/utils/promptDelivery.js.map +1 -1
  554. package/dist/utils/sentKindsSummary.d.ts +10 -0
  555. package/dist/utils/sentKindsSummary.d.ts.map +1 -0
  556. package/dist/utils/sentKindsSummary.js +18 -0
  557. package/dist/utils/sentKindsSummary.js.map +1 -0
  558. package/dist/utils/telegramError.d.ts +6 -0
  559. package/dist/utils/telegramError.d.ts.map +1 -1
  560. package/dist/utils/telegramError.js +12 -0
  561. package/dist/utils/telegramError.js.map +1 -1
  562. package/dist/utils/topicView.d.ts +1 -1
  563. package/dist/utils/topicView.js +1 -1
  564. package/dist/utils/transcribeAudio.d.ts +10 -0
  565. package/dist/utils/transcribeAudio.d.ts.map +1 -1
  566. package/dist/utils/transcribeAudio.js +22 -3
  567. package/dist/utils/transcribeAudio.js.map +1 -1
  568. package/dist/utils/transcriptErrorTime.d.ts +8 -0
  569. package/dist/utils/transcriptErrorTime.d.ts.map +1 -0
  570. package/dist/utils/transcriptErrorTime.js +56 -0
  571. package/dist/utils/transcriptErrorTime.js.map +1 -0
  572. package/dist/utils/wedgeRecovery.d.ts +2 -0
  573. package/dist/utils/wedgeRecovery.d.ts.map +1 -1
  574. package/dist/utils/wedgeRecovery.js +1 -1
  575. package/dist/utils/wedgeRecovery.js.map +1 -1
  576. package/package.json +11 -3
  577. package/remoteRun/runner.sh +1242 -0
  578. package/stt/gigaam_sidecar.py +555 -50
@@ -2,38 +2,38 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createCompaction = createCompaction;
4
4
  const telegraf_1 = require("telegraf");
5
- const createAdapter_1 = require("../../../adapters/createAdapter");
6
5
  // Decides whether the D3 summary guidance rides the compaction instruction: OpenCode bakes it into its fork
7
6
  // compaction prompt (covering auto/overflow compaction too), the Claude backends need it every time.
8
7
  const adapterNames_1 = require("../../../adapters/adapterNames");
9
8
  const sessionKey_1 = require("../../../sessionKey");
10
9
  const sessionKeyCodec_1 = require("../sessionKeyCodec");
11
- const rateLimiter_1 = require("../../../rateLimiter");
12
10
  const i18n_1 = require("../../../i18n");
13
- const sendErrorClassifier_1 = require("../../../sendErrorClassifier");
14
11
  const compactCommandRoute_1 = require("../../../utils/compactCommandRoute");
15
12
  const compactOnIdle_1 = require("../../../utils/compactOnIdle");
16
13
  const questionKeyboards_1 = require("../questionKeyboards");
17
14
  const messageSplit_1 = require("../messageSplit");
15
+ const openCodeQuestionFlow_1 = require("../../../openCodeQuestionFlow");
16
+ const callbackMessage_1 = require("./callbackMessage");
17
+ const callbackToast_1 = require("./callbackToast");
18
18
  /** The literal slash command forwarded to backends whose own CLI parses it. */
19
19
  const compactCommandText = '/compact';
20
20
  /** `adapter.name` of the raw-shell backend (see the `terminal` adapter). */
21
21
  const terminalAdapterName = 'terminal';
22
22
  /**
23
23
  * @description Compose the per-invocation compaction instruction for a thread's
24
- * backend (D3 summary guidance + the loaded-skills guidance + the optional F2
25
- * closing section). The general guidance is skipped for OpenCode (baked in its
26
- * fork prompt) and appended for the Claude backends; the skills guidance is
27
- * skipped when the backend's own compaction prompt already gets it (an OpenCode
28
- * server running the bot's compaction plugin); the closing-section directive
29
- * rides only when requested.
24
+ * backend (D3 summary guidance + the resume guidance — loaded skills and the
25
+ * next-steps checklist — + the optional F2 closing section). The general guidance
26
+ * is skipped for OpenCode (baked in its fork prompt) and appended for the Claude
27
+ * backends; the resume guidance is skipped when the backend's own compaction
28
+ * prompt already gets it (an OpenCode server running the bot's compaction
29
+ * plugin); the closing-section directive rides only when requested.
30
30
  */
31
31
  async function getCompactionInstruction(adapter, key, opts) {
32
32
  return (0, compactOnIdle_1.buildCompactionInstruction)({
33
33
  bakesSummaryGuidance: adapter.name === adapterNames_1.openCodeAdapterName,
34
34
  summaryGuidance: compactOnIdle_1.compactionSummaryGuidance,
35
- bakesSkillsGuidance: (await adapter.checkHasCompactionSkillsHook?.(key)) ?? false,
36
- skillsGuidance: compactOnIdle_1.compactionSkillsGuidance,
35
+ bakesResumeGuidance: (await adapter.checkHasCompactionResumeHook?.(key)) ?? false,
36
+ resumeGuidance: compactOnIdle_1.compactionResumeGuidance,
37
37
  closingSectionInstruction: opts.withClosingSection
38
38
  ? (0, i18n_1.t)('compact.closingSectionInstruction', {
39
39
  startMarker: compactOnIdle_1.compactionClosingStartMarker,
@@ -50,21 +50,6 @@ async function getCompactionInstruction(adapter, key, opts) {
50
50
  // ONE compaction path (plan 2026-09-14-self-compact-and-compact-on-idle).
51
51
  /** F1 poll cadence while waiting for the current turn to finish before draining. */
52
52
  const deferredCompactionPollMs = 3_000;
53
- /**
54
- * @description Resolve the `/compact` route for a thread's current backend — the
55
- * pure {@link getCompactCommandRoute} wrapped with the live adapter reading.
56
- * Every compaction entry point (the command, the seam, the F1 arm + drain, the
57
- * `/compact_on_idle` gate) asks the same question, so the three-line lookup lives
58
- * in ONE place rather than being spelled out at each of them.
59
- */
60
- function getThreadCompactRoute(key) {
61
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
62
- return (0, compactCommandRoute_1.getCompactCommandRoute)({
63
- hasCompactContext: Boolean(adapter.compactContext),
64
- adapterName: adapter.name,
65
- terminalAdapterName,
66
- });
67
- }
68
53
  /**
69
54
  * Build the `/compact_on_idle` picker keyboard: Enable / Disable, then Show / Hide
70
55
  * summary, ✓ on the current value of each.
@@ -87,7 +72,22 @@ function buildCompactOnIdleKeyboard(picker) {
87
72
  * state the typing loop and the request probe read, and the `register…()` calls.
88
73
  */
89
74
  function createCompaction(ports) {
90
- const { startTypingLoader, forwardPromptToAgent, pendingQuestions, clearPendingQuestion, idleWindowMs, checkIsLimitWaitArmed, suspendThreadSession, resumeSleepingSessionForCompaction, bot, command, getState, replyToThread, deleteThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, wakeSleepingSession } = ports;
75
+ const { startTypingLoader, forwardPromptToAgent, pendingQuestions, clearPendingQuestion, closeQuestionMessage, idleWindowMs, checkIsLimitWaitArmed, suspendThreadSession, resumeSleepingSessionForCompaction, getThreadAdapter, deliverLabel, resumeSleepingSessionForPrompt, checkIsStarting, closeOrphanQuestionMessage, notePersonActivity, bot, command, getState, replyToThread, editThreadKeyboard, authoriseContext, withThreadLocale, checkIsGeneral, wakeSleepingSession } = ports;
76
+ /**
77
+ * @description Resolve the `/compact` route for a thread's current backend — the
78
+ * pure {@link getCompactCommandRoute} wrapped with the live adapter reading.
79
+ * Every compaction entry point (the command, the seam, the F1 arm + drain, the
80
+ * `/compact_on_idle` gate) asks the same question, so the three-line lookup lives
81
+ * in ONE place rather than being spelled out at each of them.
82
+ */
83
+ function getThreadCompactRoute(key) {
84
+ const adapter = getThreadAdapter(key);
85
+ return (0, compactCommandRoute_1.getCompactCommandRoute)({
86
+ hasCompactContext: Boolean(adapter.compactContext),
87
+ adapterName: adapter.name,
88
+ terminalAdapterName,
89
+ });
90
+ }
91
91
  /**
92
92
  * Threads with a compaction IN FLIGHT (via {@link runThreadCompaction}). While a
93
93
  * key is here, the compaction's OWN output (some backends stream the summary
@@ -122,7 +122,7 @@ function createCompaction(ports) {
122
122
  * stop here could kill a loader a CONCURRENT prompt had armed.
123
123
  */
124
124
  async function runThreadCompaction(key, opts) {
125
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
125
+ const adapter = getThreadAdapter(key);
126
126
  if (!adapter.checkIsActive(key))
127
127
  return { ok: false, error: (0, i18n_1.t)('compact.start_agent_first') };
128
128
  const route = getThreadCompactRoute(key);
@@ -202,7 +202,7 @@ function createCompaction(ports) {
202
202
  // a dead session as its first act, and a "compacting…" ahead of that refusal is a
203
203
  // promise the very next message retracts. The seam still re-checks, so a session
204
204
  // lost right afterwards just reports the failure as before.
205
- if (isInTopic && (0, compactOnIdle_1.checkShouldAnnounceCompactionStart)({ route, isSessionActive: (0, createAdapter_1.getThreadAdapter)(key).checkIsActive(key) })) {
205
+ if (isInTopic && (0, compactOnIdle_1.checkShouldAnnounceCompactionStart)({ route, isSessionActive: getThreadAdapter(key).checkIsActive(key) })) {
206
206
  await replyToThread(key, (0, i18n_1.t)('compact.started'));
207
207
  }
208
208
  // No closing section: the operator is right here, so there is nothing to recap
@@ -320,7 +320,7 @@ function createCompaction(ports) {
320
320
  if (threadsCompacting.has(kStr))
321
321
  return;
322
322
  clearIdleFireTimer(kStr);
323
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
323
+ const adapter = getThreadAdapter(key);
324
324
  const isSuspendable = typeof adapter.suspendSession === 'function';
325
325
  if (!isSuspendable && !getState().checkIsCompactOnIdleEnabled(key))
326
326
  return;
@@ -414,7 +414,7 @@ function createCompaction(ports) {
414
414
  if (pollTimer)
415
415
  clearTimeout(pollTimer);
416
416
  deferredCompactionPollTimers.delete(kStr);
417
- reAskedQuestionOptions.delete(kStr);
417
+ closeReAskedQuestion(key);
418
418
  }
419
419
  /**
420
420
  * @description Whether the topic's process is WORKING right now (L-D2). The
@@ -448,7 +448,7 @@ function createCompaction(ports) {
448
448
  }
449
449
  async function onIdleCompactionTimerFired(key) {
450
450
  const kStr = (0, sessionKey_1.keyToString)(key);
451
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
451
+ const adapter = getThreadAdapter(key);
452
452
  // L-D7: a sleeping per-turn session is resumed for its compaction; the stop below puts it back to sleep.
453
453
  if (!adapter.checkIsActive(key) && adapter.name === adapterNames_1.claudePerTurnAdapterName && checkIsCompactionDue(key)) {
454
454
  if (!(await resumeSleepingSessionForCompaction(key))) {
@@ -532,17 +532,26 @@ function createCompaction(ports) {
532
532
  await getState().setCompactIdleCompactedAt(key);
533
533
  // D1: a pending question at idle → reject it server-side to UNBLOCK the turn
534
534
  // (reusing each backend's abort-error swallow via rejectQuestion + SIGINT),
535
- // remember it, drop the bot-side pending state + its now-stale buttons, then
536
- // RE-ASK it after the compaction with real buttons that feed a fresh prompt.
537
- let savedQuestion = null;
535
+ // remember it, drop the bot-side pending state (and its pin), then RE-ASK it after the compaction with real buttons
536
+ // that feed a fresh prompt. The old message stays as the record, but is edited only once the re-ask is there
537
+ // ({@link postIdleCompactionResult}): the compaction can take minutes, and «asked again below» would be untrue.
538
+ let reAsked = null;
538
539
  if (hasPendingQuestion) {
539
- savedQuestion = pendingQuestions.get(kStr) ?? null;
540
- const staleQuestionMessageId = savedQuestion?.messageId ?? null;
540
+ const savedQuestion = pendingQuestions.get(kStr) ?? null;
541
541
  adapter.rejectQuestion?.(key);
542
542
  adapter.sendSignal(key, 'SIGINT');
543
543
  clearPendingQuestion(key);
544
- if (staleQuestionMessageId !== null) {
545
- await deleteThreadMessage(key, staleQuestionMessageId).catch(() => { });
544
+ const question = savedQuestion?.data.questions[savedQuestion.currentIndex];
545
+ if (savedQuestion && question) {
546
+ // From here the question lives in a record of its own, SAVED (a restart during the compaction asks it again at boot,
547
+ // or ends the old message as unanswered when the conversation is gone, instead of forgetting it), and a person who
548
+ // writes, a conversation that ends or a number typed
549
+ // reach it like they reach any re-asked question — so someone who writes meanwhile also stops the re-ask.
550
+ reAsked = startReAskedQuestion(key, question, savedQuestion.messageId);
551
+ // Its buttons are dead from here (the question is no longer pending) and the compaction can take minutes: they come
552
+ // off at once. The text waits for the re-ask's outcome.
553
+ if (savedQuestion.messageId !== null)
554
+ void editThreadKeyboard(key, savedQuestion.messageId, null, 'idleReask');
546
555
  }
547
556
  }
548
557
  const result = await runThreadCompaction(key, { withClosingSection: true });
@@ -551,31 +560,290 @@ function createCompaction(ports) {
551
560
  // The compaction failed but the question is already rejected — re-ask it so
552
561
  // the user isn't left without the pending decision (no notice: nothing was
553
562
  // compacted).
554
- await postIdleCompactionResult(key, { noticeText: null, summary: null }, savedQuestion);
563
+ await postIdleCompactionResult(key, { noticeText: null, summary: null }, reAsked);
555
564
  return;
556
565
  }
557
566
  await postIdleCompactionResult(key, {
558
567
  noticeText: (0, i18n_1.t)('compactOnIdle.notice'),
559
568
  summary: result.summary ?? null,
560
- }, savedQuestion);
569
+ }, reAsked);
570
+ }
571
+ /**
572
+ * @description Per-thread idle-compaction RE-ASKED question (D1), keyed by {@link SessionKey} string — the record of
573
+ * {@link ReAskedQuestion}, from the start of the compaction on. A `reask_<idx>` tap, or an option NUMBER typed, forwards the
574
+ * label of its option to the (now compacted) session as a FRESH prompt — the original question request was rejected
575
+ * server-side, so it cannot be answered any more — and writes the answer under the question. When the conversation ends
576
+ * instead ({@link closeReAskedQuestion}), or a person writes something else, the message is closed as unanswered, so its
577
+ * buttons never sit there dead nor get tapped later onto whatever the agent asks next. The record is also kept in the saved
578
+ * state, so a restart finds it: its buttons keep working while the conversation sleeps ({@link restoreReAskedQuestion}),
579
+ * and a restart during the compaction asks the question again ({@link reAskInterruptedQuestion}) while the conversation can
580
+ * still be resumed, or closes the old message as unanswered when it is gone.
581
+ */
582
+ const reAskedQuestions = new Map();
583
+ /**
584
+ * The record as the saved state keeps it: the posted re-ask's message, or — before it is posted — the message the question
585
+ * waited in, flagged as such. `null` when there is no message to remember.
586
+ */
587
+ function buildSavedReAskedQuestion(reAsked) {
588
+ if (reAsked.messageId !== null) {
589
+ // The old message goes with it, and whether its result has landed: a restart between the post and that edit finishes it.
590
+ return reAsked.predecessorMessageId === null
591
+ ? { messageId: reAsked.messageId, question: reAsked.question }
592
+ : { messageId: reAsked.messageId, question: reAsked.question, predecessorMessageId: reAsked.predecessorMessageId, isPredecessorClosed: reAsked.isPredecessorEditLanded };
593
+ }
594
+ if (reAsked.predecessorMessageId !== null)
595
+ return { messageId: reAsked.predecessorMessageId, question: reAsked.question, isPosted: false };
596
+ return null;
597
+ }
598
+ /**
599
+ * The messages of a topic that already carry a result of a re-asked question — an answer a tap or a number took, or a close that
600
+ * landed — most recent last. A button can still be tapped on such a message from a stale view (a second tap on the spinner the first
601
+ * one left): it must not be taken for a message the bot knows nothing about, whose own text would be rewritten over the result.
602
+ */
603
+ const endedMessageIds = new Map();
604
+ const endedMessageLimit = 100;
605
+ function rememberEndedMessage(key, messageId) {
606
+ if (messageId === null)
607
+ return;
608
+ const kStr = (0, sessionKey_1.keyToString)(key);
609
+ const messageIds = endedMessageIds.get(kStr) ?? new Set();
610
+ messageIds.delete(messageId);
611
+ messageIds.add(messageId);
612
+ if (messageIds.size > endedMessageLimit) {
613
+ const oldest = messageIds.values().next().value;
614
+ if (oldest !== undefined)
615
+ messageIds.delete(oldest);
616
+ }
617
+ endedMessageIds.set(kStr, messageIds);
618
+ }
619
+ function forgetEndedMessage(key, messageId) {
620
+ if (messageId !== null)
621
+ endedMessageIds.get((0, sessionKey_1.keyToString)(key))?.delete(messageId);
622
+ }
623
+ function checkIsEndedMessage(key, messageId) {
624
+ return endedMessageIds.get((0, sessionKey_1.keyToString)(key))?.has(messageId) ?? false;
625
+ }
626
+ /** Give a message of the re-asked question its result through the bot's close; one that landed is remembered as ended. */
627
+ async function closeReAskedMessage(key, messageId, question, ending) {
628
+ const isEdited = await closeQuestionMessage(key, messageId, question, ending);
629
+ if (isEdited)
630
+ rememberEndedMessage(key, messageId);
631
+ return isEdited;
632
+ }
633
+ /** Keep the record in the saved state; a failed write is logged, the in-memory record is what a tap reads. */
634
+ function saveReAskedQuestion(key, reAsked) {
635
+ const record = buildSavedReAskedQuestion(reAsked);
636
+ if (record === null)
637
+ return;
638
+ getState().setReAskedQuestion(key, record).catch((e) => console.error('[reAskedQuestion] persist failed:', e));
639
+ }
640
+ /** Drop the re-asked question from the saved state; a failed write is logged. */
641
+ function forgetSavedReAskedQuestion(key) {
642
+ getState().clearReAskedQuestion(key).catch((e) => console.error('[reAskedQuestion] clear failed:', e));
643
+ }
644
+ /**
645
+ * The idle compaction takes a pending question over: the record starts in its COMPACTING phase, saved at once. The old
646
+ * message is the predecessor (`null` when it never got an id).
647
+ */
648
+ function startReAskedQuestion(key, question, predecessorMessageId) {
649
+ const reAsked = {
650
+ question,
651
+ messageId: null,
652
+ predecessorMessageId,
653
+ isPredecessorClosed: false,
654
+ isPredecessorEditLanded: false,
655
+ ending: null,
656
+ isAnswerPending: false,
657
+ sendPhase: 'notStarted',
658
+ hasIdleResultRun: false,
659
+ };
660
+ reAskedQuestions.set((0, sessionKey_1.keyToString)(key), reAsked);
661
+ saveReAskedQuestion(key, reAsked);
662
+ return reAsked;
663
+ }
664
+ /** The record is over without a result to write (its send failed): out of memory and of the saved state. */
665
+ function dropReAskedQuestion(key) {
666
+ reAskedQuestions.delete((0, sessionKey_1.keyToString)(key));
667
+ forgetSavedReAskedQuestion(key);
668
+ }
669
+ /** The re-asked question a `reask_<idx>` tap or a typed number works on, still kept. */
670
+ function peekReAskedQuestion(key) {
671
+ return reAskedQuestions.get((0, sessionKey_1.keyToString)(key));
672
+ }
673
+ /**
674
+ * @description A tap takes the re-asked question out of memory and of the saved state: it is used now, so nothing closes
675
+ * its message as unanswered any more (the tap writes the answer there), and a restart does not bring it back. An `ending`
676
+ * is what a re-ask still being sent writes on its message when it lands (the end of the conversation).
677
+ */
678
+ function takeReAskedQuestion(key, ending = null) {
679
+ const kStr = (0, sessionKey_1.keyToString)(key);
680
+ const reAsked = reAskedQuestions.get(kStr);
681
+ if (!reAsked)
682
+ return undefined;
683
+ reAskedQuestions.delete(kStr);
684
+ reAsked.ending = ending;
685
+ forgetSavedReAskedQuestion(key);
686
+ return reAsked;
687
+ }
688
+ /**
689
+ * @description A number typed answers the question: the record is taken like a tap's, with no ending yet and
690
+ * {@link ReAskedQuestion.isAnswerPending} set. The label may still reach no session, and then the question is open again, so
691
+ * the answer is written by {@link answerReAskedQuestion} once the delivery is known.
692
+ */
693
+ function takeReAskedQuestionForAnswer(key) {
694
+ const reAsked = takeReAskedQuestion(key);
695
+ if (!reAsked)
696
+ return undefined;
697
+ reAsked.isAnswerPending = true;
698
+ rememberAnsweredMessages(key, reAsked, reAsked.messageId);
699
+ return reAsked;
700
+ }
701
+ /**
702
+ * A tap or a number took the question to answer it: the copy (`copyMessageId`, which a tap may know before the record does) and
703
+ * the old message are going to carry that answer, so a second tap on either — from a stale view, while the first is still being
704
+ * delivered — answers nothing and rewrites nothing.
705
+ */
706
+ function rememberAnsweredMessages(key, reAsked, copyMessageId) {
707
+ rememberEndedMessage(key, copyMessageId);
708
+ rememberEndedMessage(key, reAsked.predecessorMessageId);
709
+ }
710
+ /**
711
+ * @description The label of a taken question reached the agent: write `answer` as its result on its messages — the re-ask's
712
+ * own if it is known (one that lands later ends the same way), and the predecessor if it has no result yet.
713
+ */
714
+ async function answerReAskedQuestion(key, reAsked, answer) {
715
+ reAsked.isAnswerPending = false;
716
+ reAsked.ending = { answer };
717
+ await endReAskedQuestion(key, reAsked);
718
+ }
719
+ /**
720
+ * A tap whose label reached no session leaves the question open: the record goes back, unless a newer one took its place.
721
+ * Resolves whether it went back — when not, the tapped message is no longer the question that waits.
722
+ */
723
+ function putBackReAskedQuestion(key, reAsked) {
724
+ if (reAskedQuestions.has((0, sessionKey_1.keyToString)(key)))
725
+ return false;
726
+ reAsked.ending = null;
727
+ reAsked.isAnswerPending = false;
728
+ reAskedQuestions.set((0, sessionKey_1.keyToString)(key), reAsked);
729
+ saveReAskedQuestion(key, reAsked);
730
+ return true;
731
+ }
732
+ /**
733
+ * Boot: a saved re-asked question whose conversation can still be resumed gets its buttons back (memory only, it is saved already).
734
+ * A restart between the post of the copy and the edit of the old message leaves the old message without a note: it gets it now.
735
+ */
736
+ function restoreReAskedQuestion(key, saved) {
737
+ const isPredecessorSettled = saved.predecessorMessageId === undefined || saved.predecessorMessageId === null || saved.isPredecessorClosed !== false;
738
+ const reAsked = {
739
+ question: saved.question,
740
+ messageId: saved.messageId,
741
+ predecessorMessageId: saved.predecessorMessageId ?? null,
742
+ isPredecessorClosed: isPredecessorSettled,
743
+ isPredecessorEditLanded: isPredecessorSettled,
744
+ ending: null,
745
+ isAnswerPending: false,
746
+ sendPhase: 'finished',
747
+ hasIdleResultRun: true,
748
+ };
749
+ reAskedQuestions.set((0, sessionKey_1.keyToString)(key), reAsked);
750
+ settleReAskedPredecessor(key, reAsked).catch((e) => console.error('[reAskedQuestion] settling the old message at boot failed:', e));
751
+ }
752
+ /**
753
+ * Boot: a saved re-asked question that cannot be brought back (its conversation is gone, or a question waits beside it) ends:
754
+ * its message — and the old message, if the restart left that without a note — is closed as unanswered, never left with buttons that
755
+ * do nothing or would answer the wrong question, and the saved record is dropped. An unposted record names the old message itself.
756
+ */
757
+ function closeSavedReAskedQuestion(key, saved) {
758
+ forgetSavedReAskedQuestion(key);
759
+ const messageIds = [saved.messageId];
760
+ if (saved.isPosted !== false && saved.predecessorMessageId != null && saved.isPredecessorClosed === false)
761
+ messageIds.push(saved.predecessorMessageId);
762
+ for (const messageId of messageIds) {
763
+ closeReAskedMessage(key, messageId, saved.question, 'unanswered')
764
+ .catch((e) => console.error('[reAskedQuestion] closing the saved question failed:', e));
765
+ }
766
+ }
767
+ /**
768
+ * @description Write the record's `ending` on its messages: the re-ask's own, if it is known, and the predecessor, if it has
769
+ * no result yet (a re-ask that ends before it is posted ends the question where it waited). A copy that lands later is closed
770
+ * by {@link postReAskedQuestion} with the same ending.
771
+ */
772
+ async function endReAskedQuestion(key, reAsked) {
773
+ const ending = reAsked.ending;
774
+ if (ending === null)
775
+ return;
776
+ if (reAsked.messageId !== null)
777
+ await closeReAskedMessage(key, reAsked.messageId, reAsked.question, ending);
778
+ if (reAsked.predecessorMessageId !== null && !reAsked.isPredecessorClosed) {
779
+ reAsked.isPredecessorClosed = true;
780
+ await closeReAskedMessage(key, reAsked.predecessorMessageId, reAsked.question, ending);
781
+ }
561
782
  }
562
783
  /**
563
- * @description Per-thread option labels of an idle-compaction RE-ASKED question
564
- * (D1), keyed by {@link SessionKey} string. A `reask_<idx>` tap forwards the
565
- * matching label to the (now compacted) session as a FRESH prompt — the original
566
- * question request was rejected server-side, so it cannot be answered any more.
567
- * In-memory only: after a restart the buttons are simply inert ("no pending
568
- * question"), which is safe.
784
+ * @description The re-asked question will not be tapped any more — its conversation ended (session closed or stopped,
785
+ * `/quit`, `/quitall`, `/new`, leaving the folder, another conversation picked), a person wrote something else, or the
786
+ * agent asked a newer question: forget it and close its message as unanswered — the question stays, the buttons go. In
787
+ * the compaction's own phase the message that closes is the one the question waited in. A re-ask whose send is still in
788
+ * flight has no message id yet: its ending is set, and the send closes the message when it lands. A question used by a
789
+ * tap is no longer here, so its answer is never overwritten.
569
790
  */
570
- const reAskedQuestionOptions = new Map();
791
+ function closeReAskedQuestion(key) {
792
+ const reAsked = takeReAskedQuestion(key, 'unanswered');
793
+ if (!reAsked)
794
+ return;
795
+ endReAskedQuestion(key, reAsked).catch((e) => console.error('[reAskedQuestion] close failed:', e));
796
+ }
571
797
  /**
572
- * @description Build the RE-ASK inline keyboard for an idle-compaction pending
573
- * question (D1) and record its option labels so a `reask_<idx>` tap can forward
574
- * the chosen label as a fresh prompt.
798
+ * @description Post the RE-ASKED question (D1) with its option buttons — the record is in the map already, in its COMPACTING
799
+ * phase, so a `reask_<idx>` tap that lands right after the send finds it and can forward the chosen label as a fresh
800
+ * prompt. Its message id is recorded once the send resolves (and the record saved, unless a tap or an ending took it
801
+ * meanwhile). Resolves that id — `null` when the send failed, and then there is no record either: nothing was posted that
802
+ * a tap could be on.
575
803
  */
576
- function buildReAskKeyboard(key, question) {
577
- reAskedQuestionOptions.set((0, sessionKey_1.keyToString)(key), question.options.map((opt) => opt.label));
578
- return (0, questionKeyboards_1.buildQuestionOptionsKeyboard)(question, (optionIndex) => `reask_${optionIndex}`);
804
+ async function postReAskedQuestion(key, text, reAsked) {
805
+ const keyboard = (0, questionKeyboards_1.buildQuestionOptionsKeyboard)(reAsked.question, (optionIndex) => `reask_${optionIndex}`);
806
+ reAsked.sendPhase = 'inFlight';
807
+ const sentMessageId = await replyToThread(key, text, (0, questionKeyboards_1.buildKeyboardExtra)(keyboard));
808
+ reAsked.sendPhase = 'finished';
809
+ const isStillKept = reAskedQuestions.get((0, sessionKey_1.keyToString)(key)) === reAsked;
810
+ if (sentMessageId === null) {
811
+ if (isStillKept)
812
+ dropReAskedQuestion(key);
813
+ return null;
814
+ }
815
+ reAsked.messageId = sentMessageId;
816
+ // The conversation ended (or a number answered) while the send was in flight: the message that landed ends the same way.
817
+ if (reAsked.ending !== null)
818
+ await endReAskedQuestion(key, reAsked);
819
+ else if (isStillKept)
820
+ saveReAskedQuestion(key, reAsked);
821
+ // Taken to be answered (a tap or a number): the copy that landed is going to carry the answer.
822
+ else
823
+ rememberEndedMessage(key, sentMessageId);
824
+ return sentMessageId;
825
+ }
826
+ /** The text of the re-asked question's message: the sentence that says it is asked again, then the question (its options are the buttons). */
827
+ function buildReAskedQuestionText(question) {
828
+ return [
829
+ (0, i18n_1.t)('compactOnIdle.pendingQuestionReask'),
830
+ question.header ? `${question.header}\n${question.question}` : question.question,
831
+ ].join('\n\n');
832
+ }
833
+ /**
834
+ * @description Boot: the bot stopped between the start of an idle compaction and the re-ask of its question, so `saved` names
835
+ * the message the question waited in (its buttons off, no result yet). The question was rejected on the agent's side, and
836
+ * nobody has had the chance to answer it — so, when the conversation can still be resumed, it is asked again NOW, by the very
837
+ * post the idle fire makes, and the old message says so. A re-ask that cannot be sent ends the old message as unanswered, as
838
+ * in the idle fire.
839
+ */
840
+ async function reAskInterruptedQuestion(key, saved) {
841
+ await withThreadLocale(key, async () => {
842
+ const reAsked = startReAskedQuestion(key, saved.question, saved.messageId);
843
+ reAsked.hasIdleResultRun = true;
844
+ await postReAskedQuestion(key, buildReAskedQuestionText(saved.question), reAsked);
845
+ await settleReAskedPredecessor(key, reAsked);
846
+ });
579
847
  }
580
848
  /**
581
849
  * @description Post the idle-compaction outcome (D1/D2) as SEPARATE messages in
@@ -590,15 +858,12 @@ function createCompaction(ports) {
590
858
  *
591
859
  * `parts.noticeText` is `null` on a compaction failure (re-ask only, no notice).
592
860
  * No-op when there is nothing at all to say.
861
+ *
862
+ * The re-ask is posted only if its record is still kept: someone who wrote while the compaction ran, a conversation that
863
+ * ended, or a number typed has ended it already, and then the old message has its result.
593
864
  */
594
- async function postIdleCompactionResult(key, parts, savedQuestion) {
595
- const question = savedQuestion?.data.questions[savedQuestion.currentIndex];
596
- const questionText = question
597
- ? [
598
- (0, i18n_1.t)('compactOnIdle.pendingQuestionReask'),
599
- question.header ? `${question.header}\n${question.question}` : question.question,
600
- ].join('\n\n')
601
- : null;
865
+ async function postIdleCompactionResult(key, parts, reAsked) {
866
+ const questionText = reAsked ? buildReAskedQuestionText(reAsked.question) : null;
602
867
  // A tracker issue has no topic to narrate into (R6): the compaction and the stop stay silent there.
603
868
  if (!(0, sessionKeyCodec_1.checkIsTelegramKey)(key))
604
869
  return;
@@ -607,11 +872,175 @@ function createCompaction(ports) {
607
872
  await replyToThread(key, plan.notice);
608
873
  if (plan.summary)
609
874
  await postCompactionSummary(key, plan.summary);
610
- // The keyboard is built HERE (not while planning): it also records the option
611
- // labels a `reask_<idx>` tap resolves against, which must not happen for a
612
- // question that is never posted.
613
- if (plan.question && question)
614
- await replyToThread(key, plan.question, (0, questionKeyboards_1.buildKeyboardExtra)(buildReAskKeyboard(key, question)));
875
+ if (!reAsked)
876
+ return;
877
+ // From here the re-ask is decided: a record taken meanwhile (a number typed) is not posted, and whoever puts it back finishes it.
878
+ reAsked.hasIdleResultRun = true;
879
+ if (plan.question && peekReAskedQuestion(key) === reAsked)
880
+ await postReAskedQuestion(key, plan.question, reAsked);
881
+ await settleReAskedPredecessor(key, reAsked);
882
+ }
883
+ /**
884
+ * @description The old question message stays as the record. It says "asked again below" only once there IS a copy below — if
885
+ * the re-ask could not be sent, nobody answered it and nobody will be asked again. An ending written on it already (a person
886
+ * wrote, the conversation ended) stands, and so does the result of a number whose delivery is under way: that answer
887
+ * writes it, or puts the question back and settles the predecessor again.
888
+ */
889
+ async function settleReAskedPredecessor(key, reAsked) {
890
+ if (reAsked.ending !== null || reAsked.isAnswerPending || reAsked.predecessorMessageId === null || reAsked.isPredecessorClosed)
891
+ return;
892
+ reAsked.isPredecessorClosed = true;
893
+ const isEdited = await closeReAskedMessage(key, reAsked.predecessorMessageId, reAsked.question, reAsked.messageId !== null ? 'askedAgain' : 'unanswered');
894
+ if (!isEdited)
895
+ return;
896
+ // Saved only now, so a restart before the edit landed finds the old message unsettled and finishes it.
897
+ reAsked.isPredecessorEditLanded = true;
898
+ if (reAskedQuestions.get((0, sessionKey_1.keyToString)(key)) === reAsked)
899
+ saveReAskedQuestion(key, reAsked);
900
+ }
901
+ /**
902
+ * @description A tap on a button of the question the idle compaction asked again (D1). The original request was rejected
903
+ * on the agent's side during the compaction, so the chosen option's label is delivered to the (now compacted) conversation
904
+ * as a FRESH prompt, not answered. Once it is delivered the re-asked message stays as the record: the question as asked,
905
+ * the chosen answer in place of the buttons.
906
+ */
907
+ async function handleReAskedQuestionTap(ctx, optionIndex) {
908
+ const key = await authoriseContext(ctx);
909
+ if (!key) {
910
+ await ctx.answerCbQuery((0, i18n_1.t)('cb.access_denied'));
911
+ return;
912
+ }
913
+ const tappedMessageId = (0, callbackMessage_1.getCallbackMessageId)(ctx);
914
+ const reAsked = peekReAskedQuestion(key);
915
+ // A re-ask whose send has not returned has no message id, and when the old message has none either there is nothing to tell
916
+ // the tapped message from an older copy by: no tap is taken until the id is known, and none is rewritten.
917
+ if (reAsked !== undefined && reAsked.messageId === null && reAsked.predecessorMessageId === null) {
918
+ await ctx.answerCbQuery((0, i18n_1.t)('cb.no_pending_question'));
919
+ return;
920
+ }
921
+ // The tap must be on the message of the question that waits now. A button left on an OLDER re-asked message (its close
922
+ // failed, or it predates a restart) would answer the newer question with that question's own label: it answers nothing,
923
+ // and its buttons come off. A re-ask whose send has not returned has no message id yet — the tap is on that message if
924
+ // it is NEWER than the one the question waited in before (the re-ask is always posted after it); older ones are the stale ones.
925
+ const isTappedMessageCurrent = reAsked !== undefined && (reAsked.messageId !== null
926
+ ? reAsked.messageId === tappedMessageId
927
+ : tappedMessageId !== null && reAsked.predecessorMessageId !== null && tappedMessageId > reAsked.predecessorMessageId);
928
+ if (!isTappedMessageCurrent) {
929
+ await ctx.answerCbQuery((0, i18n_1.t)('cb.no_pending_question'));
930
+ // A message that already carries a result (an answer a first tap is still delivering, a close that landed) is left as it is:
931
+ // a second tap from a stale view must not rewrite it. Only a message the bot knows nothing about is closed here.
932
+ if (tappedMessageId === null || checkIsEndedMessage(key, tappedMessageId))
933
+ return;
934
+ rememberEndedMessage(key, tappedMessageId);
935
+ // Not awaited: the edit waits its turn in the send pacer, and the next update of the topic waits for this handler. Marked
936
+ // first, so two quick taps do not both close it; a close that did not land leaves live buttons that do nothing, so the
937
+ // mark is taken back and the next tap tries again.
938
+ closeOrphanQuestionMessage(key, tappedMessageId, (0, callbackMessage_1.getCallbackMessageText)(ctx))
939
+ .then((isClosed) => { if (!isClosed)
940
+ forgetEndedMessage(key, tappedMessageId); })
941
+ .catch((e) => {
942
+ forgetEndedMessage(key, tappedMessageId);
943
+ console.error('[reask] closing the tapped orphan failed:', e);
944
+ });
945
+ return;
946
+ }
947
+ const label = reAsked.question.options[optionIndex]?.label;
948
+ if (!label) {
949
+ await ctx.answerCbQuery((0, i18n_1.t)('cb.invalid_option'));
950
+ return;
951
+ }
952
+ // The question is taken before anything is awaited and put back if the label reaches no one: from here on it is this
953
+ // tap's, so no other path can use it or close it under the answer.
954
+ takeReAskedQuestion(key);
955
+ const reAskedMessageId = reAsked.messageId ?? tappedMessageId;
956
+ rememberAnsweredMessages(key, reAsked, reAskedMessageId);
957
+ // The idle fire posts the re-asked question just BEFORE it stops the process, and a stopping process still reads as
958
+ // active: a tap inside that window (or inside a start) is buffered like a typed prompt and replayed once a session is up.
959
+ // The answer to a re-asked question wakes a sleeping conversation (L-D4) — the question is its own.
960
+ if (!checkIsStarting(key) && !getThreadAdapter(key).checkIsActive(key) && !(await resumeSleepingSessionForPrompt(key))) {
961
+ await returnReAskedQuestionAfterTap(key, reAsked, reAskedMessageId);
962
+ await ctx.answerCbQuery((0, i18n_1.t)('agent.no_session'));
963
+ return;
964
+ }
965
+ // The closed question carries the whole label, so the toast may cut it; it is posted whole only if that close fails.
966
+ const isLabelCut = await (0, callbackToast_1.answerCallbackWithFittedText)(ctx, label);
967
+ // An answer to the agent's own question is never a request (S7), even delivered as a prompt. Routed at delivery,
968
+ // not on the `checkIsStarting` read above: the stop may have ended during the toast, and a label buffered into its closed
969
+ // window was never replayed.
970
+ const outcome = await deliverLabel(key, label);
971
+ // The label reached no session (the topic was told why): the question is still open, so are its buttons.
972
+ if (outcome === 'noSession') {
973
+ await returnReAskedQuestionAfterTap(key, reAsked, reAskedMessageId);
974
+ return;
975
+ }
976
+ const isAnswerShown = reAskedMessageId !== null && await closeReAskedMessage(key, reAskedMessageId, reAsked.question, { answer: label });
977
+ if (!isAnswerShown && isLabelCut)
978
+ await replyToThread(key, label);
979
+ }
980
+ /**
981
+ * @description A bare option NUMBER typed while the question the idle compaction asks again waits — for its re-ask (the
982
+ * compaction runs: the buttons are off, the numbered options are still on screen) or for a tap — answers it, like a tap on
983
+ * that option: the label goes to the agent as a fresh prompt and the answer is written under the question. Without this the
984
+ * number would count as the person writing something else: the question would end «not answered» and the agent would get a
985
+ * bare «2». Resolves whether the text was such a number (and was handled).
986
+ */
987
+ async function answerReAskedQuestionByNumber(key, text) {
988
+ const reAsked = peekReAskedQuestion(key);
989
+ if (!reAsked)
990
+ return false;
991
+ const route = (0, openCodeQuestionFlow_1.getQuestionReplyRoute)(text, reAsked.question);
992
+ if (route.kind !== 'answer')
993
+ return false;
994
+ const label = route.labels[0];
995
+ // Taken before anything is awaited, so no other path uses or closes it, but with no result written yet: the label may reach no
996
+ // session, and a re-ask that lands meanwhile is left as it is until that is known.
997
+ takeReAskedQuestionForAnswer(key);
998
+ // A person wrote — the latch clears like for any message; the record is gone, so nothing is closed as «not answered».
999
+ notePersonActivity(key);
1000
+ const outcome = await deliverLabel(key, label);
1001
+ if (outcome === 'noSession') {
1002
+ const isPutBack = await returnReAskedQuestionAfterTap(key, reAsked, reAsked.messageId ?? reAsked.predecessorMessageId);
1003
+ if (isPutBack)
1004
+ await finishReAskAfterNumberFoundNoSession(key, reAsked);
1005
+ return true;
1006
+ }
1007
+ await answerReAskedQuestion(key, reAsked, label);
1008
+ return true;
1009
+ }
1010
+ /**
1011
+ * @description A typed number found no session and the question is open again. The idle fire decides about the re-ask once, and
1012
+ * it skips a record a number has taken (an answer was on its way): so what the fire left undone is done here, once it has run.
1013
+ * - the fire has not run yet — it will post the re-ask itself, now that the record is back;
1014
+ * - its send is in flight — the fire settles the old message when the copy lands;
1015
+ * - the fire ran and sent nothing — the re-ask is posted now, and the old message points at it (or ends «not answered» if the
1016
+ * send fails);
1017
+ * - the copy is there already, the old message waiting for the answer's outcome — it says where the question went.
1018
+ */
1019
+ async function finishReAskAfterNumberFoundNoSession(key, reAsked) {
1020
+ if (!reAsked.hasIdleResultRun || reAsked.sendPhase === 'inFlight')
1021
+ return;
1022
+ if (reAsked.sendPhase === 'notStarted') {
1023
+ await withThreadLocale(key, () => postReAskedQuestion(key, buildReAskedQuestionText(reAsked.question), reAsked));
1024
+ }
1025
+ await settleReAskedPredecessor(key, reAsked);
1026
+ // A send that failed leaves nothing to answer: the question ended on the old message.
1027
+ if (reAsked.messageId === null && peekReAskedQuestion(key) === reAsked)
1028
+ dropReAskedQuestion(key);
1029
+ }
1030
+ /**
1031
+ * @description A tap on a re-asked question whose label reached no session leaves the question open: its record goes back.
1032
+ * Not when a newer re-ask has taken its place meanwhile — the tapped message is not the question that waits any more,
1033
+ * so it is closed as unanswered instead of keeping buttons that would answer the wrong question. Resolves whether it went back.
1034
+ */
1035
+ async function returnReAskedQuestionAfterTap(key, reAsked, messageId) {
1036
+ if (putBackReAskedQuestion(key, reAsked)) {
1037
+ // The answer did not happen: the message is an open question again, not one that carries a result.
1038
+ forgetEndedMessage(key, messageId);
1039
+ return true;
1040
+ }
1041
+ if (messageId !== null)
1042
+ await closeReAskedMessage(key, messageId, reAsked.question, 'unanswered');
1043
+ return false;
615
1044
  }
616
1045
  /**
617
1046
  * @description F1: arm a "compact when the current turn finishes" request from
@@ -620,7 +1049,7 @@ function createCompaction(ports) {
620
1049
  * compact). The drain (below) runs the plain compaction once the session idles.
621
1050
  */
622
1051
  function armDeferredCompaction(key) {
623
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
1052
+ const adapter = getThreadAdapter(key);
624
1053
  if (!adapter.checkIsActive(key)) {
625
1054
  return { ok: false, message: 'No active agent session in this topic — nothing to compact.' };
626
1055
  }
@@ -648,7 +1077,7 @@ function createCompaction(ports) {
648
1077
  const kStr = (0, sessionKey_1.keyToString)(key);
649
1078
  if (!deferredCompactionArmed.has(kStr))
650
1079
  return;
651
- const adapter = (0, createAdapter_1.getThreadAdapter)(key);
1080
+ const adapter = getThreadAdapter(key);
652
1081
  if (!adapter.checkIsActive(key)) {
653
1082
  deferredCompactionArmed.delete(kStr);
654
1083
  return;
@@ -716,17 +1145,10 @@ function createCompaction(ports) {
716
1145
  await ctx.answerCbQuery();
717
1146
  await withThreadLocale(key, () => apply(key, isGeneral));
718
1147
  // Re-render the picker keyboard so the ✓ follows the new state.
719
- const cbMsg = ctx.callbackQuery?.message;
720
- if (cbMsg) {
1148
+ const pickerMessageId = (0, callbackMessage_1.getCallbackMessageId)(ctx);
1149
+ if (pickerMessageId !== null) {
721
1150
  const keyboard = withThreadLocale(key, () => buildCompactOnIdleKeyboard(getCompactOnIdlePickerState(key, isGeneral)));
722
- try {
723
- await (0, rateLimiter_1.enqueueSend)(key, () => bot.telegram.editMessageReplyMarkup((0, sessionKeyCodec_1.getTelegramChatId)(key), cbMsg.message_id, undefined, keyboard.reply_markup));
724
- }
725
- catch (e) {
726
- const desc = (0, sendErrorClassifier_1.checkIsApiError)(e) ? (0, sendErrorClassifier_1.getErrorDescription)(e) : '';
727
- if (!/message is not modified/i.test(desc))
728
- console.warn('[coi_cb] keyboard re-render failed:', desc || e);
729
- }
1151
+ await editThreadKeyboard(key, pickerMessageId, keyboard.reply_markup, 'coi_cb');
730
1152
  }
731
1153
  }
732
1154
  /**
@@ -771,18 +1193,9 @@ function createCompaction(ports) {
771
1193
  await withThreadLocale(key, async () => {
772
1194
  await replyToThread(key, await applyCompactSummary(key, isGeneral, enabled));
773
1195
  });
774
- const cbMsg = ctx.callbackQuery?.message;
775
- if (cbMsg) {
776
- const keyboard = buildCompactSummaryKeyboard(enabled);
777
- try {
778
- await (0, rateLimiter_1.enqueueSend)(key, () => bot.telegram.editMessageReplyMarkup((0, sessionKeyCodec_1.getTelegramChatId)(key), cbMsg.message_id, undefined, keyboard.reply_markup));
779
- }
780
- catch (e) {
781
- const desc = (0, sendErrorClassifier_1.checkIsApiError)(e) ? (0, sendErrorClassifier_1.getErrorDescription)(e) : '';
782
- if (!/message is not modified/i.test(desc))
783
- console.warn('[csum_cb] keyboard re-render failed:', desc || e);
784
- }
785
- }
1196
+ const pickerMessageId = (0, callbackMessage_1.getCallbackMessageId)(ctx);
1197
+ if (pickerMessageId !== null)
1198
+ await editThreadKeyboard(key, pickerMessageId, buildCompactSummaryKeyboard(enabled).reply_markup, 'csum_cb');
786
1199
  }
787
1200
  function registerCompactionCommands() {
788
1201
  // `/compact` — shrink the agent's context. BOT-OWNED, because "forward the text
@@ -798,7 +1211,7 @@ function createCompaction(ports) {
798
1211
  // first: a terminal topic with no live shell would otherwise be told to start an
799
1212
  // agent instead of that the backend cannot be compacted.
800
1213
  if (route === 'notSupported') {
801
- await replyToThread(key, (0, i18n_1.t)('compact.unsupported_backend', { label: (0, createAdapter_1.getThreadAdapter)(key).label }));
1214
+ await replyToThread(key, (0, i18n_1.t)('compact.unsupported_backend', { label: getThreadAdapter(key).label }));
802
1215
  return;
803
1216
  }
804
1217
  // A sleeping conversation is woken first (L-D4): the compaction runs on its own context.
@@ -823,7 +1236,7 @@ function createCompaction(ports) {
823
1236
  const arg = parsed.args.join(' ').toLowerCase();
824
1237
  const isGeneral = checkIsGeneral(key);
825
1238
  if (!isGeneral) {
826
- if (!(0, createAdapter_1.getThreadAdapter)(key).checkIsActive(key) || getThreadCompactRoute(key) === 'notSupported') {
1239
+ if (!getThreadAdapter(key).checkIsActive(key) || getThreadCompactRoute(key) === 'notSupported') {
827
1240
  await replyToThread(key, (0, i18n_1.t)('compactOnIdle.unsupported'));
828
1241
  return;
829
1242
  }
@@ -880,6 +1293,7 @@ function createCompaction(ports) {
880
1293
  }));
881
1294
  bot.action('csum_on', (ctx) => handleCompactSummaryCallback(ctx, true));
882
1295
  bot.action('csum_off', (ctx) => handleCompactSummaryCallback(ctx, false));
1296
+ bot.action(/^reask_(\d+)$/, (ctx) => handleReAskedQuestionTap(ctx, parseInt(ctx.match[1], 10)));
883
1297
  }
884
1298
  return {
885
1299
  checkIsThreadCompacting,
@@ -891,7 +1305,11 @@ function createCompaction(ports) {
891
1305
  armDeferredCompaction,
892
1306
  checkIsDeferredCompactionArmed,
893
1307
  threadsCompacting,
894
- reAskedQuestionOptions,
1308
+ answerReAskedQuestionByNumber,
1309
+ reAskInterruptedQuestion,
1310
+ restoreReAskedQuestion,
1311
+ closeSavedReAskedQuestion,
1312
+ closeReAskedQuestion,
895
1313
  registerCompactionCommands,
896
1314
  registerCompactionCallbacks,
897
1315
  };