charness 3.2.0 โ†’ 3.3.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 (313) hide show
  1. package/.env.example +3 -6
  2. package/DEVELOPMENT.md +15 -0
  3. package/README.md +111 -38
  4. package/SETUP.md +121 -16
  5. package/dist/adapters/adapterEventRouting.d.ts +2 -1
  6. package/dist/adapters/adapterEventRouting.d.ts.map +1 -1
  7. package/dist/adapters/adapterEventRouting.js +3 -0
  8. package/dist/adapters/adapterEventRouting.js.map +1 -1
  9. package/dist/adapters/adapterNames.d.ts +13 -0
  10. package/dist/adapters/adapterNames.d.ts.map +1 -1
  11. package/dist/adapters/adapterNames.js +16 -0
  12. package/dist/adapters/adapterNames.js.map +1 -1
  13. package/dist/adapters/backendUnavailableError.d.ts +11 -0
  14. package/dist/adapters/backendUnavailableError.d.ts.map +1 -0
  15. package/dist/adapters/backendUnavailableError.js +19 -0
  16. package/dist/adapters/backendUnavailableError.js.map +1 -0
  17. package/dist/adapters/claudeCliAdapter.d.ts +29 -6
  18. package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
  19. package/dist/adapters/claudeCliAdapter.js +70 -16
  20. package/dist/adapters/claudeCliAdapter.js.map +1 -1
  21. package/dist/adapters/claudeJsonStreamAdapter.d.ts +36 -3
  22. package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
  23. package/dist/adapters/claudeJsonStreamAdapter.js +93 -30
  24. package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
  25. package/dist/adapters/createAdapter.d.ts +3 -14
  26. package/dist/adapters/createAdapter.d.ts.map +1 -1
  27. package/dist/adapters/createAdapter.js +9 -19
  28. package/dist/adapters/createAdapter.js.map +1 -1
  29. package/dist/adapters/openCodeAdapter.d.ts +140 -12
  30. package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
  31. package/dist/adapters/openCodeAdapter.js +385 -119
  32. package/dist/adapters/openCodeAdapter.js.map +1 -1
  33. package/dist/apiErrorRetry.d.ts +70 -6
  34. package/dist/apiErrorRetry.d.ts.map +1 -1
  35. package/dist/apiErrorRetry.js +312 -34
  36. package/dist/apiErrorRetry.js.map +1 -1
  37. package/dist/apiRetryKick.d.ts +26 -1
  38. package/dist/apiRetryKick.d.ts.map +1 -1
  39. package/dist/apiRetryKick.js +41 -2
  40. package/dist/apiRetryKick.js.map +1 -1
  41. package/dist/bot.d.ts +2 -1
  42. package/dist/bot.d.ts.map +1 -1
  43. package/dist/bot.js +736 -184
  44. package/dist/bot.js.map +1 -1
  45. package/dist/cli/claudeProfiles.d.ts +1 -1
  46. package/dist/cli/claudeProfiles.js +1 -1
  47. package/dist/cli/hot.d.ts +7 -1
  48. package/dist/cli/hot.d.ts.map +1 -1
  49. package/dist/cli/hot.js +29 -3
  50. package/dist/cli/hot.js.map +1 -1
  51. package/dist/configDirFile.d.ts +73 -0
  52. package/dist/configDirFile.d.ts.map +1 -0
  53. package/dist/configDirFile.js +216 -0
  54. package/dist/configDirFile.js.map +1 -0
  55. package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +2 -2
  56. package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -1
  57. package/dist/connectors/telegram/commands/autoContinueLimits.js +4 -4
  58. package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -1
  59. package/dist/connectors/telegram/commands/backendSwitch.d.ts +25 -0
  60. package/dist/connectors/telegram/commands/backendSwitch.d.ts.map +1 -0
  61. package/dist/connectors/telegram/commands/backendSwitch.js +104 -0
  62. package/dist/connectors/telegram/commands/backendSwitch.js.map +1 -0
  63. package/dist/connectors/telegram/commands/callbackMessage.d.ts +12 -0
  64. package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
  65. package/dist/connectors/telegram/commands/callbackMessage.js +24 -0
  66. package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
  67. package/dist/connectors/telegram/commands/callbackToast.d.ts +36 -0
  68. package/dist/connectors/telegram/commands/callbackToast.d.ts.map +1 -0
  69. package/dist/connectors/telegram/commands/callbackToast.js +83 -0
  70. package/dist/connectors/telegram/commands/callbackToast.js.map +1 -0
  71. package/dist/connectors/telegram/commands/claudeAccounts.d.ts +18 -0
  72. package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -1
  73. package/dist/connectors/telegram/commands/claudeAccounts.js +220 -66
  74. package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -1
  75. package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
  76. package/dist/connectors/telegram/commands/displayModes.js +4 -2
  77. package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
  78. package/dist/connectors/telegram/commands/fallbackChain.d.ts +5 -8
  79. package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -1
  80. package/dist/connectors/telegram/commands/fallbackChain.js +183 -193
  81. package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
  82. package/dist/connectors/telegram/commands/limitsBoard.d.ts +40 -0
  83. package/dist/connectors/telegram/commands/limitsBoard.d.ts.map +1 -0
  84. package/dist/connectors/telegram/commands/limitsBoard.js +281 -0
  85. package/dist/connectors/telegram/commands/limitsBoard.js.map +1 -0
  86. package/dist/connectors/telegram/commands/modelPage.d.ts +130 -0
  87. package/dist/connectors/telegram/commands/modelPage.d.ts.map +1 -0
  88. package/dist/connectors/telegram/commands/modelPage.js +120 -0
  89. package/dist/connectors/telegram/commands/modelPage.js.map +1 -0
  90. package/dist/connectors/telegram/commands/modelProviders.d.ts +9 -1
  91. package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -1
  92. package/dist/connectors/telegram/commands/modelProviders.js +203 -111
  93. package/dist/connectors/telegram/commands/modelProviders.js.map +1 -1
  94. package/dist/connectors/telegram/commands/stt.d.ts +32 -0
  95. package/dist/connectors/telegram/commands/stt.d.ts.map +1 -0
  96. package/dist/connectors/telegram/commands/stt.js +460 -0
  97. package/dist/connectors/telegram/commands/stt.js.map +1 -0
  98. package/dist/effortLevels.d.ts +57 -7
  99. package/dist/effortLevels.d.ts.map +1 -1
  100. package/dist/effortLevels.js +65 -9
  101. package/dist/effortLevels.js.map +1 -1
  102. package/dist/i18n/de.d.ts.map +1 -1
  103. package/dist/i18n/de.js +160 -17
  104. package/dist/i18n/de.js.map +1 -1
  105. package/dist/i18n/en.d.ts.map +1 -1
  106. package/dist/i18n/en.js +160 -17
  107. package/dist/i18n/en.js.map +1 -1
  108. package/dist/i18n/es.d.ts.map +1 -1
  109. package/dist/i18n/es.js +160 -17
  110. package/dist/i18n/es.js.map +1 -1
  111. package/dist/i18n/fr.d.ts.map +1 -1
  112. package/dist/i18n/fr.js +160 -17
  113. package/dist/i18n/fr.js.map +1 -1
  114. package/dist/i18n/hi.d.ts.map +1 -1
  115. package/dist/i18n/hi.js +160 -17
  116. package/dist/i18n/hi.js.map +1 -1
  117. package/dist/i18n/ja.d.ts.map +1 -1
  118. package/dist/i18n/ja.js +160 -17
  119. package/dist/i18n/ja.js.map +1 -1
  120. package/dist/i18n/ka.d.ts.map +1 -1
  121. package/dist/i18n/ka.js +160 -17
  122. package/dist/i18n/ka.js.map +1 -1
  123. package/dist/i18n/pt.d.ts.map +1 -1
  124. package/dist/i18n/pt.js +160 -17
  125. package/dist/i18n/pt.js.map +1 -1
  126. package/dist/i18n/ru.d.ts.map +1 -1
  127. package/dist/i18n/ru.js +160 -17
  128. package/dist/i18n/ru.js.map +1 -1
  129. package/dist/i18n/uk.d.ts.map +1 -1
  130. package/dist/i18n/uk.js +160 -17
  131. package/dist/i18n/uk.js.map +1 -1
  132. package/dist/i18n/uz.d.ts.map +1 -1
  133. package/dist/i18n/uz.js +160 -17
  134. package/dist/i18n/uz.js.map +1 -1
  135. package/dist/i18n/zh.d.ts.map +1 -1
  136. package/dist/i18n/zh.js +160 -17
  137. package/dist/i18n/zh.js.map +1 -1
  138. package/dist/installManager.d.ts +20 -4
  139. package/dist/installManager.d.ts.map +1 -1
  140. package/dist/installManager.js +82 -14
  141. package/dist/installManager.js.map +1 -1
  142. package/dist/instanceSettings.d.ts +52 -0
  143. package/dist/instanceSettings.d.ts.map +1 -0
  144. package/dist/instanceSettings.js +136 -0
  145. package/dist/instanceSettings.js.map +1 -0
  146. package/dist/pinnedStatus.d.ts +7 -1
  147. package/dist/pinnedStatus.d.ts.map +1 -1
  148. package/dist/pinnedStatus.js +1 -1
  149. package/dist/pinnedStatus.js.map +1 -1
  150. package/dist/requests/requestLedger.d.ts +8 -0
  151. package/dist/requests/requestLedger.d.ts.map +1 -1
  152. package/dist/requests/requestLedger.js +19 -1
  153. package/dist/requests/requestLedger.js.map +1 -1
  154. package/dist/scheduler/mcpSurface.js +4 -4
  155. package/dist/scheduler/mcpSurface.js.map +1 -1
  156. package/dist/shutdown.d.ts +14 -0
  157. package/dist/shutdown.d.ts.map +1 -1
  158. package/dist/shutdown.js +16 -0
  159. package/dist/shutdown.js.map +1 -1
  160. package/dist/state.d.ts +54 -2
  161. package/dist/state.d.ts.map +1 -1
  162. package/dist/state.js +62 -1
  163. package/dist/state.js.map +1 -1
  164. package/dist/stt/sttAutoInstall.d.ts +39 -0
  165. package/dist/stt/sttAutoInstall.d.ts.map +1 -0
  166. package/dist/stt/sttAutoInstall.js +158 -0
  167. package/dist/stt/sttAutoInstall.js.map +1 -0
  168. package/dist/stt/sttChildEnv.d.ts +11 -0
  169. package/dist/stt/sttChildEnv.d.ts.map +1 -0
  170. package/dist/stt/sttChildEnv.js +30 -0
  171. package/dist/stt/sttChildEnv.js.map +1 -0
  172. package/dist/stt/sttConfigEdit.d.ts +71 -0
  173. package/dist/stt/sttConfigEdit.d.ts.map +1 -0
  174. package/dist/stt/sttConfigEdit.js +144 -0
  175. package/dist/stt/sttConfigEdit.js.map +1 -0
  176. package/dist/stt/sttInstall.d.ts +147 -0
  177. package/dist/stt/sttInstall.d.ts.map +1 -0
  178. package/dist/stt/sttInstall.js +491 -0
  179. package/dist/stt/sttInstall.js.map +1 -0
  180. package/dist/stt/sttInstallEntry.d.ts +2 -0
  181. package/dist/stt/sttInstallEntry.d.ts.map +1 -0
  182. package/dist/stt/sttInstallEntry.js +31 -0
  183. package/dist/stt/sttInstallEntry.js.map +1 -0
  184. package/dist/stt/sttInstallReporter.d.ts +45 -0
  185. package/dist/stt/sttInstallReporter.d.ts.map +1 -0
  186. package/dist/stt/sttInstallReporter.js +57 -0
  187. package/dist/stt/sttInstallReporter.js.map +1 -0
  188. package/dist/stt/sttRuntime.d.ts +35 -0
  189. package/dist/stt/sttRuntime.d.ts.map +1 -0
  190. package/dist/stt/sttRuntime.js +73 -0
  191. package/dist/stt/sttRuntime.js.map +1 -0
  192. package/dist/stt/sttScreenPlan.d.ts +193 -0
  193. package/dist/stt/sttScreenPlan.d.ts.map +1 -0
  194. package/dist/stt/sttScreenPlan.js +541 -0
  195. package/dist/stt/sttScreenPlan.js.map +1 -0
  196. package/dist/stt/sttSettings.d.ts +201 -0
  197. package/dist/stt/sttSettings.d.ts.map +1 -0
  198. package/dist/stt/sttSettings.js +387 -0
  199. package/dist/stt/sttSettings.js.map +1 -0
  200. package/dist/stt/sttSidecarOwner.d.ts +115 -0
  201. package/dist/stt/sttSidecarOwner.d.ts.map +1 -0
  202. package/dist/stt/sttSidecarOwner.js +475 -0
  203. package/dist/stt/sttSidecarOwner.js.map +1 -0
  204. package/dist/stt/transcribeChain.d.ts +36 -0
  205. package/dist/stt/transcribeChain.d.ts.map +1 -0
  206. package/dist/stt/transcribeChain.js +42 -0
  207. package/dist/stt/transcribeChain.js.map +1 -0
  208. package/dist/types.d.ts +77 -4
  209. package/dist/types.d.ts.map +1 -1
  210. package/dist/utils/adapterSwitchSessionIds.d.ts +32 -0
  211. package/dist/utils/adapterSwitchSessionIds.d.ts.map +1 -0
  212. package/dist/utils/adapterSwitchSessionIds.js +44 -0
  213. package/dist/utils/adapterSwitchSessionIds.js.map +1 -0
  214. package/dist/utils/backendSwitchPlan.d.ts +63 -0
  215. package/dist/utils/backendSwitchPlan.d.ts.map +1 -0
  216. package/dist/utils/backendSwitchPlan.js +73 -0
  217. package/dist/utils/backendSwitchPlan.js.map +1 -0
  218. package/dist/utils/claudeAccountPickerPlan.d.ts +12 -1
  219. package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -1
  220. package/dist/utils/claudeAccountPickerPlan.js +20 -1
  221. package/dist/utils/claudeAccountPickerPlan.js.map +1 -1
  222. package/dist/utils/claudeTranscriptContent.d.ts +24 -0
  223. package/dist/utils/claudeTranscriptContent.d.ts.map +1 -0
  224. package/dist/utils/claudeTranscriptContent.js +107 -0
  225. package/dist/utils/claudeTranscriptContent.js.map +1 -0
  226. package/dist/utils/claudeUsage.d.ts +47 -8
  227. package/dist/utils/claudeUsage.d.ts.map +1 -1
  228. package/dist/utils/claudeUsage.js +76 -14
  229. package/dist/utils/claudeUsage.js.map +1 -1
  230. package/dist/utils/claudeUsageRefresher.d.ts +8 -1
  231. package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
  232. package/dist/utils/claudeUsageRefresher.js +9 -4
  233. package/dist/utils/claudeUsageRefresher.js.map +1 -1
  234. package/dist/utils/effortPickerPlan.d.ts +54 -0
  235. package/dist/utils/effortPickerPlan.d.ts.map +1 -0
  236. package/dist/utils/effortPickerPlan.js +56 -0
  237. package/dist/utils/effortPickerPlan.js.map +1 -0
  238. package/dist/utils/ensureSessionPlan.d.ts +20 -4
  239. package/dist/utils/ensureSessionPlan.d.ts.map +1 -1
  240. package/dist/utils/ensureSessionPlan.js +17 -4
  241. package/dist/utils/ensureSessionPlan.js.map +1 -1
  242. package/dist/utils/fallbackBridge.d.ts +6 -2
  243. package/dist/utils/fallbackBridge.d.ts.map +1 -1
  244. package/dist/utils/fallbackBridge.js +10 -3
  245. package/dist/utils/fallbackBridge.js.map +1 -1
  246. package/dist/utils/fallbackChain.d.ts +36 -0
  247. package/dist/utils/fallbackChain.d.ts.map +1 -1
  248. package/dist/utils/fallbackChain.js +65 -1
  249. package/dist/utils/fallbackChain.js.map +1 -1
  250. package/dist/utils/fallbackSwitchNotice.d.ts +41 -0
  251. package/dist/utils/fallbackSwitchNotice.d.ts.map +1 -0
  252. package/dist/utils/fallbackSwitchNotice.js +75 -0
  253. package/dist/utils/fallbackSwitchNotice.js.map +1 -0
  254. package/dist/utils/fileSendService.d.ts +33 -10
  255. package/dist/utils/fileSendService.d.ts.map +1 -1
  256. package/dist/utils/fileSendService.js +78 -27
  257. package/dist/utils/fileSendService.js.map +1 -1
  258. package/dist/utils/jsonStreamHost.d.ts +39 -0
  259. package/dist/utils/jsonStreamHost.d.ts.map +1 -1
  260. package/dist/utils/jsonStreamHost.js +60 -1
  261. package/dist/utils/jsonStreamHost.js.map +1 -1
  262. package/dist/utils/limitEpisodeRecovery.d.ts +16 -2
  263. package/dist/utils/limitEpisodeRecovery.d.ts.map +1 -1
  264. package/dist/utils/limitEpisodeRecovery.js +44 -3
  265. package/dist/utils/limitEpisodeRecovery.js.map +1 -1
  266. package/dist/utils/limitSettings.d.ts +25 -0
  267. package/dist/utils/limitSettings.d.ts.map +1 -0
  268. package/dist/utils/limitSettings.js +31 -0
  269. package/dist/utils/limitSettings.js.map +1 -0
  270. package/dist/utils/limitsBoardPlan.d.ts +52 -0
  271. package/dist/utils/limitsBoardPlan.d.ts.map +1 -0
  272. package/dist/utils/limitsBoardPlan.js +93 -0
  273. package/dist/utils/limitsBoardPlan.js.map +1 -0
  274. package/dist/utils/openCodeUsage.d.ts +93 -0
  275. package/dist/utils/openCodeUsage.d.ts.map +1 -0
  276. package/dist/utils/openCodeUsage.js +248 -0
  277. package/dist/utils/openCodeUsage.js.map +1 -0
  278. package/dist/utils/operatorSwitchPlan.d.ts +61 -0
  279. package/dist/utils/operatorSwitchPlan.d.ts.map +1 -0
  280. package/dist/utils/operatorSwitchPlan.js +32 -0
  281. package/dist/utils/operatorSwitchPlan.js.map +1 -0
  282. package/dist/utils/promptDelivery.d.ts +40 -0
  283. package/dist/utils/promptDelivery.d.ts.map +1 -1
  284. package/dist/utils/promptDelivery.js +31 -0
  285. package/dist/utils/promptDelivery.js.map +1 -1
  286. package/dist/utils/recvPreviewRedaction.d.ts +14 -4
  287. package/dist/utils/recvPreviewRedaction.d.ts.map +1 -1
  288. package/dist/utils/recvPreviewRedaction.js +19 -7
  289. package/dist/utils/recvPreviewRedaction.js.map +1 -1
  290. package/dist/utils/startupReadiness.d.ts +3 -3
  291. package/dist/utils/startupReadiness.d.ts.map +1 -1
  292. package/dist/utils/startupReadiness.js +2 -2
  293. package/dist/utils/startupReadiness.js.map +1 -1
  294. package/dist/utils/threadStatusReport.d.ts +3 -1
  295. package/dist/utils/threadStatusReport.d.ts.map +1 -1
  296. package/dist/utils/threadStatusReport.js +4 -3
  297. package/dist/utils/threadStatusReport.js.map +1 -1
  298. package/dist/utils/timezone.d.ts +23 -0
  299. package/dist/utils/timezone.d.ts.map +1 -1
  300. package/dist/utils/timezone.js +38 -0
  301. package/dist/utils/timezone.js.map +1 -1
  302. package/dist/utils/transcribeAudio.d.ts +25 -12
  303. package/dist/utils/transcribeAudio.d.ts.map +1 -1
  304. package/dist/utils/transcribeAudio.js +39 -21
  305. package/dist/utils/transcribeAudio.js.map +1 -1
  306. package/dist/utils/usageRefreshBudget.d.ts +9 -0
  307. package/dist/utils/usageRefreshBudget.d.ts.map +1 -0
  308. package/dist/utils/usageRefreshBudget.js +27 -0
  309. package/dist/utils/usageRefreshBudget.js.map +1 -0
  310. package/examples/.env.example +7 -3
  311. package/examples/docker-compose.yml +9 -7
  312. package/package.json +2 -1
  313. package/stt/gigaam_sidecar.py +800 -0
@@ -4,19 +4,26 @@ exports.createClaudeAccounts = createClaudeAccounts;
4
4
  /**
5
5
  * `/accounts` โ€” every account the bot works with, on one screen: the Claude accounts (profiles) with their usage
6
6
  * (which one a topic's Claude sessions run on, add / re-login / remove) and the OpenCode providers the server is
7
- * authenticated for (connect / disconnect). `/account`, `/connect` and `/disconnect` stay as hidden aliases; the
8
- * OpenCode flows themselves live in `modelProviders.ts` and are only entered from here through ports. See
9
- * README.md in this folder and `utils/claudeProfiles.ts`.
7
+ * authenticated for (switch the topic to one through the shared model page / connect / disconnect; a ChatGPT
8
+ * subscription shows its usage like a Claude account). The title and the โœ“ say where the topic REALLY runs, OpenCode
9
+ * included. `/account`, `/connect` and `/disconnect` stay as hidden aliases; the OpenCode flows themselves live in
10
+ * `modelProviders.ts` and are only entered from here through ports. See README.md in this folder and
11
+ * `utils/claudeProfiles.ts`.
10
12
  */
11
13
  const telegraf_1 = require("telegraf");
12
14
  const sessionKey_1 = require("../../../sessionKey");
13
15
  const i18n_1 = require("../../../i18n");
16
+ const fallbackChain_1 = require("../../../utils/fallbackChain");
17
+ const openCodeUsage_1 = require("../../../utils/openCodeUsage");
18
+ const modelPickerPlan_1 = require("../../../utils/modelPickerPlan");
14
19
  const claudeProfiles_1 = require("../../../utils/claudeProfiles");
15
20
  const claudeProfileUsageLines_1 = require("../../../utils/claudeProfileUsageLines");
16
21
  const claudeUsageRefresher_1 = require("../../../utils/claudeUsageRefresher");
17
22
  const claudeAccountPickerPlan_1 = require("../../../utils/claudeAccountPickerPlan");
18
23
  const openCodeAdapter_1 = require("../../../adapters/openCodeAdapter");
19
24
  const callbackMessage_1 = require("./callbackMessage");
25
+ const callbackToast_1 = require("./callbackToast");
26
+ const modelPage_1 = require("./modelPage");
20
27
  /** Screens whose provider snapshot is kept (the newest); an older keyboard is refused as expired. */
21
28
  const providerSnapshotLimit = 50;
22
29
  /**
@@ -25,7 +32,8 @@ const providerSnapshotLimit = 50;
25
32
  * auth-status sink the sign-in driver reports to.
26
33
  */
27
34
  function createClaudeAccounts(ports) {
28
- const { bot, command, getState, replyToThread, editThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, getDataDir, getInstanceTimezone, refreshClaudeUsage, readClaudeAuthStatus, startClaudeProfileLogin, applyClaudeProfile, applyClaudeProfileEverywhere, detectOpenCodeProviders, disconnectOpenCodeProvider, startOpenCodeConnect, applyAutoSwitch, getHomeClaudeDir, } = ports;
35
+ const { bot, command, getState, replyToThread, editThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, getDataDir, getInstanceTimezone, refreshClaudeUsage, readClaudeAuthStatus, startClaudeProfileLogin, applyClaudeProfile, applyClaudeProfileEverywhere, switchTopicByOperator, detectOpenCodeProviders, listOpenCodeModels, getTopicRun, disconnectOpenCodeProvider, startOpenCodeConnect, applyAutoSwitch, getHomeClaudeDir, } = ports;
36
+ const answerWithWholeText = (0, callbackToast_1.createWholeTextAnswerer)(replyToThread);
29
37
  /**
30
38
  * Topics whose next plain message is an answer to ยซโž•ยป: the name of a new Claude profile, or the id of the
31
39
  * OpenCode provider to connect. One capture per topic; armed by the buttons / `add`, disarmed by a slash
@@ -33,16 +41,14 @@ function createClaudeAccounts(ports) {
33
41
  */
34
42
  const awaitingProfileName = new Map();
35
43
  /** The OpenCode providers each screen MESSAGE listed โ€” its disconnect buttons carry an index into this. */
44
+ /** Per MESSAGE, keyed by chat + message id (`buildMessageSnapshotKey`): a message id is unique only within one chat. */
36
45
  const providerSnapshots = new Map();
37
- function rememberProviders(messageId, providers) {
38
- providerSnapshots.delete(messageId);
39
- providerSnapshots.set(messageId, providers);
40
- while (providerSnapshots.size > providerSnapshotLimit) {
41
- const oldest = providerSnapshots.keys().next().value;
42
- if (oldest === undefined)
43
- break;
44
- providerSnapshots.delete(oldest);
45
- }
46
+ /** The provider whose model page a screen MESSAGE shows, with the models its buttons index (dropped when the screen is re-rendered). */
47
+ const pageSnapshots = new Map();
48
+ function rememberProviders(key, messageId, providers) {
49
+ const snapshotKey = (0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId);
50
+ pageSnapshots.delete(snapshotKey);
51
+ (0, callbackMessage_1.rememberBoundedSnapshot)(providerSnapshots, snapshotKey, providers, providerSnapshotLimit);
46
52
  }
47
53
  async function readOpenCodeProviders() {
48
54
  try {
@@ -53,9 +59,31 @@ function createClaudeAccounts(ports) {
53
59
  return [];
54
60
  }
55
61
  }
56
- function resolveActiveName(key, isGeneral) {
62
+ /** The Claude account a topic's next Claude session takes: its own pick, else the instance default. */
63
+ function resolveClaudeAccountName(key, isGeneral) {
57
64
  return isGeneral ? getState().getClaudeProfileGlobalDefault() : getState().resolveClaudeProfileName(key);
58
65
  }
66
+ /**
67
+ * The account the โœ“ sits on. General: the instance default. A topic: the account its Claude session RUNS on (which is
68
+ * not the pick while a switch is deferred), no account while it runs OpenCode, and โ€” with no agent โ€” the pick the next
69
+ * start takes.
70
+ */
71
+ function resolveActiveName(key, isGeneral) {
72
+ if (isGeneral)
73
+ return getState().getClaudeProfileGlobalDefault();
74
+ const run = getTopicRun(key);
75
+ if (run.family === 'opencode')
76
+ return null;
77
+ return run.rung?.backend === 'claude' ? run.rung.profile : resolveClaudeAccountName(key, false);
78
+ }
79
+ /** Where the topic runs, for the title: `Claude ยซperfยป`, `OpenCode openai/gpt-5`, or `OpenCode` while its model is unknown. */
80
+ function formatRunLabel(key) {
81
+ const run = getTopicRun(key);
82
+ if (run.family === 'opencode') {
83
+ return run.rung?.backend === 'opencode' ? `${(0, i18n_1.t)('fallback.backendOpenCode')} ${run.rung.providerId}/${run.rung.modelId}` : (0, i18n_1.t)('fallback.backendOpenCode');
84
+ }
85
+ return `${(0, i18n_1.t)('fallback.backendClaude')} ยซ${resolveActiveName(key, false) ?? resolveClaudeAccountName(key, false)}ยป`;
86
+ }
59
87
  /** The config directory of a profile; `null` for `default`. */
60
88
  function resolveDir(name) {
61
89
  return (0, claudeProfiles_1.resolveClaudeProfileDir)(getDataDir(), name);
@@ -147,33 +175,62 @@ function createClaudeAccounts(ports) {
147
175
  topicCounts,
148
176
  });
149
177
  }
178
+ /** How a provider is signed in, for its row: ยซChatGPT subscriptionยป / ยซAPI keyยป; nothing for a kind we have no word for. */
179
+ function formatProviderAuthKind(providerId) {
180
+ const kind = getState().getOpenCodeProviderUsage(providerId)?.authKind;
181
+ if (kind === 'apiKey')
182
+ return (0, i18n_1.t)('accounts.providerApiKey');
183
+ return kind === 'subscription' && providerId === openCodeUsage_1.chatGptProviderId ? (0, i18n_1.t)('accounts.providerSubscription') : '';
184
+ }
185
+ /** The connected providers as text: `โœ“ openai โ€” ChatGPT subscription`, its usage windows hanging under it like an account's. */
186
+ function formatProviderList(providers, rung) {
187
+ const nowMs = Date.now();
188
+ return providers.map((provider) => {
189
+ const kind = formatProviderAuthKind(provider);
190
+ const usageLines = (0, claudeProfileUsageLines_1.formatClaudeProfileUsageLines)(getState().getOpenCodeProviderUsage(provider), getInstanceTimezone(), nowMs);
191
+ return `${(0, fallbackChain_1.getRunningModelOfProvider)(rung, provider) === null ? 'โ€ข' : 'โœ“'} ${provider}${kind === '' ? '' : ` โ€” ${kind}`}${usageLines.map((line) => `\n${line}`).join('')}`;
192
+ }).join('\n');
193
+ }
194
+ /** A provider's pick button in a topic: `โœ“ openai ยท gpt-5` where the topic runs, the bare provider elsewhere. */
195
+ function formatProviderRowButton(provider, rung) {
196
+ const runningModel = (0, fallbackChain_1.getRunningModelOfProvider)(rung, provider);
197
+ return runningModel === null ? provider : `โœ“ ${provider} ยท ${runningModel}`;
198
+ }
150
199
  /** The main screen: text + keyboard, for the topic (or General), and the provider list the keyboard indexes. */
151
200
  async function buildPicker(key, isGeneral) {
152
201
  const [rows, providers] = await Promise.all([buildPickerRows(key, isGeneral), readOpenCodeProviders()]);
153
202
  const list = rows.map(formatRow).join('\n');
154
- const activeName = resolveActiveName(key, isGeneral);
203
+ const run = getTopicRun(key);
155
204
  const title = isGeneral
156
- ? (0, i18n_1.t)('account.titleGeneral', { name: activeName, list })
157
- : (0, i18n_1.t)('account.title', { name: activeName, list });
205
+ ? (0, i18n_1.t)('account.titleGeneral', { name: resolveClaudeAccountName(key, true), list })
206
+ : (0, i18n_1.t)('account.title', { current: formatRunLabel(key), list });
158
207
  const openCodeSection = providers.length === 0
159
208
  ? (0, i18n_1.t)('accounts.openCodeNone')
160
- : (0, i18n_1.t)('accounts.openCodeSection', { list: providers.map((provider) => `โ€ข ${provider}`).join('\n') });
209
+ : (0, i18n_1.t)('accounts.openCodeSection', { list: formatProviderList(providers, isGeneral ? null : run.rung) });
161
210
  const isDefaultRemoved = getState().getClaudeProfileInfo(claudeProfiles_1.defaultClaudeProfileName)?.isRemoved === true;
162
211
  const removedNote = isDefaultRemoved ? `\n\n${(0, i18n_1.t)('accounts.defaultRemovedNote')}` : '';
163
212
  // The usage hint (which says the percentages are USED) follows the account list it explains.
164
- const text = `${title}${removedNote}\n\n${(0, i18n_1.t)('account.usageHint')}\n\n${openCodeSection}\n\n${(0, i18n_1.t)('accounts.buttonsHint')}\n\n${(0, i18n_1.t)('account.autoSwitchHint')}`;
165
- // One row per Claude account: pick it (โœ“ = the current one), sign it in again, remove it (`default` too: signs it out).
213
+ const buttonsHint = (0, i18n_1.t)(isGeneral ? 'accounts.buttonsHintGeneral' : 'accounts.buttonsHint');
214
+ const text = `${title}${removedNote}\n\n${(0, i18n_1.t)('account.usageHint')}\n\n${openCodeSection}\n\n${buttonsHint}\n\n${(0, i18n_1.t)('account.autoSwitchHint')}`;
215
+ // One row per Claude account: pick it (โœ“ = where the topic runs), sign it in again, remove it (`default` too: signs it out).
166
216
  const keyboardRows = rows.map((row) => [
167
217
  telegraf_1.Markup.button.callback(formatRowButton(row), (0, claudeAccountPickerPlan_1.buildAccountUseCallback)(row.name)),
168
218
  telegraf_1.Markup.button.callback('๐Ÿ”‘', (0, claudeAccountPickerPlan_1.buildAccountLoginCallback)(row.name)),
169
219
  telegraf_1.Markup.button.callback('๐Ÿ—‘', (0, claudeAccountPickerPlan_1.buildAccountRemoveCallback)(row.name)),
170
220
  ]);
171
- // A provider row mirrors it: what the button does is in its caption, ๐Ÿ”‘ connects it again.
221
+ // A provider row mirrors it. In a topic its caption is the pick (the model page follows), ๐Ÿ”‘ connects it again and
222
+ // ๐Ÿ”Œ disconnects it; in General a provider can only be connected or disconnected: ยซ๐Ÿ”Œ Disconnect Xยป and ๐Ÿ”‘.
172
223
  providers.forEach((provider, index) => {
173
- keyboardRows.push([
174
- telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.disconnectButton', { provider }), (0, claudeAccountPickerPlan_1.buildAccountDisconnectCallback)(index)),
175
- telegraf_1.Markup.button.callback('๐Ÿ”‘', (0, claudeAccountPickerPlan_1.buildAccountReconnectCallback)(index)),
176
- ]);
224
+ keyboardRows.push(isGeneral
225
+ ? [
226
+ telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.disconnectButton', { provider }), (0, claudeAccountPickerPlan_1.buildAccountDisconnectCallback)(index)),
227
+ telegraf_1.Markup.button.callback('๐Ÿ”‘', (0, claudeAccountPickerPlan_1.buildAccountReconnectCallback)(index)),
228
+ ]
229
+ : [
230
+ telegraf_1.Markup.button.callback(formatProviderRowButton(provider, run.rung), (0, claudeAccountPickerPlan_1.buildAccountOpenCodeUseCallback)(index)),
231
+ telegraf_1.Markup.button.callback('๐Ÿ”‘', (0, claudeAccountPickerPlan_1.buildAccountReconnectCallback)(index)),
232
+ telegraf_1.Markup.button.callback('๐Ÿ”Œ', (0, claudeAccountPickerPlan_1.buildAccountDisconnectCallback)(index)),
233
+ ]);
177
234
  });
178
235
  keyboardRows.push([
179
236
  telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.addClaudeButton'), claudeAccountPickerPlan_1.accountAddCallback),
@@ -191,7 +248,7 @@ function createClaudeAccounts(ports) {
191
248
  const picker = await buildPicker(key, isGeneral);
192
249
  const messageId = await replyToThread(key, picker.text, picker.keyboard);
193
250
  if (messageId !== null)
194
- rememberProviders(messageId, picker.providers);
251
+ rememberProviders(key, messageId, picker.providers);
195
252
  }
196
253
  /** A sub-picker: one button per profile (`default` included only when `includeDefault`), plus ยซBackยป. */
197
254
  function buildProfileChoiceKeyboard(buildCallback, includeDefault) {
@@ -205,7 +262,7 @@ function createClaudeAccounts(ports) {
205
262
  if (messageId === null)
206
263
  return;
207
264
  const picker = await buildPicker(key, isGeneral);
208
- rememberProviders(messageId, picker.providers);
265
+ rememberProviders(key, messageId, picker.providers);
209
266
  await editThreadMessage(key, messageId, picker.text, picker.keyboard);
210
267
  }
211
268
  function describeApplyOutcome(outcome) {
@@ -216,7 +273,9 @@ function createClaudeAccounts(ports) {
216
273
  * button. A regular topic sets its own pick and its live session follows at once (idle: re-spawned, same
217
274
  * conversation; working: at turn end; sleeping: at the next resume). General sets the instance default and
218
275
  * every topic that follows the default is moved the same way. A profile that is not signed in is refused:
219
- * a session started under it would only fail with a logged-out error.
276
+ * a session started under it would only fail with a logged-out error. On a topic that runs OpenCode the pick is
277
+ * a switch to Claude (`switchTopicByOperator`, which also makes the limit move while a usage-limit wait is armed): the answer
278
+ * is then the question already posted (`null`: nothing to say) or the line that the topic switched.
220
279
  */
221
280
  async function applyPick(key, isGeneral, name) {
222
281
  if (!checkIsKnownProfile(name))
@@ -232,9 +291,8 @@ function createClaudeAccounts(ports) {
232
291
  await applyClaudeProfileEverywhere();
233
292
  return (0, i18n_1.t)('account.setGlobal', { name });
234
293
  }
235
- await getState().setClaudeProfileOverride(key, name);
236
- const outcome = await applyClaudeProfile(key);
237
- return (0, i18n_1.t)('account.setThisTopic', { name, apply: describeApplyOutcome(outcome) });
294
+ const outcome = await switchTopicByOperator(key, { backend: 'claude', profile: name, model: null });
295
+ return outcome.kind === 'asked' ? null : outcome.text;
238
296
  }
239
297
  /** A topic drops its own pick; its session follows the instance default from now on. */
240
298
  async function applyFollowDefault(key) {
@@ -376,6 +434,50 @@ function createClaudeAccounts(ports) {
376
434
  if (message)
377
435
  await replyToThread(key, message);
378
436
  }
437
+ // โ”€โ”€ a provider as a pick: its model page โ”€โ”€
438
+ /** The provider's model page in the screen's message (the shared page builder), and the snapshot its buttons index. */
439
+ async function showProviderModelPage(key, messageId, providerId, models, page) {
440
+ const shown = (0, modelPage_1.buildOpenCodeProviderPage)({
441
+ providerId,
442
+ models,
443
+ page,
444
+ rung: getTopicRun(key).rung,
445
+ limits: getState().getOpenCodeModelLimits(),
446
+ timezone: getInstanceTimezone(),
447
+ nowMs: Date.now(),
448
+ callbacks: {
449
+ buildPage: claudeAccountPickerPlan_1.buildAccountOpenCodePageCallback,
450
+ buildPick: claudeAccountPickerPlan_1.buildAccountOpenCodeModelCallback,
451
+ noop: modelPickerPlan_1.modelPickerNoopCallback,
452
+ back: { label: (0, i18n_1.t)('fallback.backButton'), data: claudeAccountPickerPlan_1.accountBackCallback },
453
+ },
454
+ });
455
+ (0, callbackMessage_1.rememberBoundedSnapshot)(pageSnapshots, (0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId), { providerId, models }, providerSnapshotLimit);
456
+ await editThreadMessage(key, messageId, shown.text, shown.keyboard);
457
+ }
458
+ /** What the shared provider-pick handlers need from this screen: the switch path, the toast and the way back to the screen. */
459
+ function buildPickDeps(ctx, key, messageId, isGeneral) {
460
+ return { ctx, key, switchTopicByOperator, answerWholeText: answerWithWholeText, rerender: () => rerenderPicker(key, messageId, isGeneral) };
461
+ }
462
+ /** `/accounts use <provider>/<model>`: the model a topic runs, through the same path as a tap; never in General. */
463
+ async function applyTypedOpenCodePick(key, isGeneral, label) {
464
+ const parsed = (0, fallbackChain_1.parseOpenCodeModelLabel)(label);
465
+ if (parsed === null) {
466
+ await replyToThread(key, (0, i18n_1.t)('account.usage'));
467
+ return;
468
+ }
469
+ if (isGeneral) {
470
+ await replyToThread(key, (0, i18n_1.t)('limits.openCodeInTopicOnly'));
471
+ return;
472
+ }
473
+ if (getState().getBinding(key) === null) {
474
+ await replyToThread(key, (0, i18n_1.t)('thread.bind_required'));
475
+ return;
476
+ }
477
+ const outcome = await switchTopicByOperator(key, { backend: 'opencode', providerId: parsed.providerId, modelId: parsed.modelId });
478
+ if (outcome.kind !== 'asked')
479
+ await replyToThread(key, outcome.text);
480
+ }
379
481
  // โ”€โ”€ the command โ”€โ”€
380
482
  function registerClaudeAccountCommands() {
381
483
  // `/accounts` (hidden alias `/account`) โ€” bare: the screen. `add <name>` creates a profile and signs it in; `use <name>` switches
@@ -406,7 +508,13 @@ function createClaudeAccounts(ports) {
406
508
  await replyToThread(key, (0, i18n_1.t)('account.usage'));
407
509
  return;
408
510
  }
409
- await replyToThread(key, await applyPick(key, isGeneral, name));
511
+ if (name.includes('/')) {
512
+ await applyTypedOpenCodePick(key, isGeneral, rest.join(' ').trim());
513
+ return;
514
+ }
515
+ const confirmation = await applyPick(key, isGeneral, name);
516
+ if (confirmation !== null)
517
+ await replyToThread(key, confirmation);
410
518
  return;
411
519
  }
412
520
  case 'login': {
@@ -446,88 +554,133 @@ function createClaudeAccounts(ports) {
446
554
  }
447
555
  await withThreadLocale(key, () => run(key, checkIsGeneral(key), (0, callbackMessage_1.getCallbackMessageId)(ctx)));
448
556
  }
449
- /** `answerCbQuery` is capped at 200 characters: a long confirmation goes to the topic instead. */
450
- async function answerTap(ctx, key, text) {
451
- if (text.length <= 180) {
452
- await ctx.answerCbQuery(text);
453
- return;
454
- }
455
- await ctx.answerCbQuery();
456
- await replyToThread(key, text);
457
- }
458
557
  function registerClaudeAccountCallbacks() {
459
558
  bot.action(claudeAccountPickerPlan_1.accountUseCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
460
559
  const name = (0, claudeAccountPickerPlan_1.parseAccountCallbackName)(ctx.match[0], claudeAccountPickerPlan_1.accountUseCallbackRe, { allowDefault: true });
461
560
  if (name === null) {
462
- await ctx.answerCbQuery();
561
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
463
562
  return;
464
563
  }
465
564
  const confirmation = await applyPick(key, isGeneral, name);
466
- await answerTap(ctx, key, confirmation);
565
+ if (confirmation === null)
566
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
567
+ else
568
+ await answerWithWholeText(ctx, key, confirmation);
467
569
  await rerenderPicker(key, messageId, isGeneral);
468
570
  }));
469
571
  bot.action(claudeAccountPickerPlan_1.accountFollowDefaultCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
470
572
  if (isGeneral) {
471
- await ctx.answerCbQuery();
573
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
472
574
  return;
473
575
  }
474
- await answerTap(ctx, key, await applyFollowDefault(key));
576
+ await answerWithWholeText(ctx, key, await applyFollowDefault(key));
475
577
  await rerenderPicker(key, messageId, isGeneral);
476
578
  }));
477
579
  bot.action(claudeAccountPickerPlan_1.accountAddCallback, (ctx) => handleTap(ctx, async (key) => {
478
580
  awaitingProfileName.set((0, sessionKey_1.keyToString)(key), 'claudeName');
479
- await ctx.answerCbQuery();
581
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
480
582
  await replyToThread(key, `${(0, i18n_1.t)('account.askName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
481
583
  }));
482
584
  bot.action(claudeAccountPickerPlan_1.accountAddProviderCallback, (ctx) => handleTap(ctx, async (key) => {
483
585
  awaitingProfileName.set((0, sessionKey_1.keyToString)(key), 'providerId');
484
- await ctx.answerCbQuery();
586
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
485
587
  await replyToThread(key, `${(0, i18n_1.t)('accounts.askProvider')}\n${(0, i18n_1.t)('login.esc_hint')}`);
486
588
  }));
487
589
  // A provider's ยซ๐Ÿ”Œยป row โ†’ ask first (a disconnect drops stored credentials), against the snapshot of the
488
590
  // message the button sits on: an older screen can never map its index onto a newer list.
489
591
  bot.action(claudeAccountPickerPlan_1.accountDisconnectCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
490
- const provider = messageId === null ? undefined : providerSnapshots.get(messageId)?.[Number(ctx.match[1])];
592
+ const provider = messageId === null ? undefined : providerSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId))?.[Number(ctx.match[1])];
491
593
  if (messageId === null || provider === undefined) {
492
- await ctx.answerCbQuery((0, i18n_1.t)('accounts.stale'));
594
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
493
595
  await rerenderPicker(key, messageId, isGeneral);
494
596
  return;
495
597
  }
496
- await ctx.answerCbQuery();
598
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
497
599
  await editThreadMessage(key, messageId, (0, i18n_1.t)('accounts.confirmDisconnect', { provider }), telegraf_1.Markup.inlineKeyboard([
498
600
  [telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.confirmDisconnectButton', { provider }), (0, claudeAccountPickerPlan_1.buildAccountDisconnectConfirmCallback)(Number(ctx.match[1])))],
499
601
  [telegraf_1.Markup.button.callback((0, i18n_1.t)('account.backButton'), claudeAccountPickerPlan_1.accountBackCallback)],
500
602
  ]));
501
603
  }));
502
604
  bot.action(claudeAccountPickerPlan_1.accountDisconnectConfirmCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
503
- const provider = messageId === null ? undefined : providerSnapshots.get(messageId)?.[Number(ctx.match[1])];
605
+ const provider = messageId === null ? undefined : providerSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId))?.[Number(ctx.match[1])];
504
606
  if (messageId === null || provider === undefined) {
505
- await ctx.answerCbQuery((0, i18n_1.t)('accounts.stale'));
607
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
506
608
  await rerenderPicker(key, messageId, isGeneral);
507
609
  return;
508
610
  }
509
- await ctx.answerCbQuery();
611
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
510
612
  await disconnectOpenCodeProvider(key, provider);
511
613
  await rerenderPicker(key, messageId, isGeneral);
512
614
  }));
513
615
  // A provider's ยซ๐Ÿ”‘ยป โ†’ connect it again (the method picker); the screen is re-rendered first.
514
616
  bot.action(claudeAccountPickerPlan_1.accountReconnectCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
515
- const provider = messageId === null ? undefined : providerSnapshots.get(messageId)?.[Number(ctx.match[1])];
617
+ const provider = messageId === null ? undefined : providerSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId))?.[Number(ctx.match[1])];
516
618
  if (messageId === null || provider === undefined) {
517
- await ctx.answerCbQuery((0, i18n_1.t)('accounts.stale'));
619
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
518
620
  await rerenderPicker(key, messageId, isGeneral);
519
621
  return;
520
622
  }
521
- await ctx.answerCbQuery();
623
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
522
624
  await startOpenCodeConnect(key, provider);
523
625
  }));
626
+ // A provider row in a topic โ†’ its model page (a provider with one model is picked at once, unless the topic runs on it);
627
+ // resolved against the snapshot of the message the button sits on, like the disconnect buttons.
628
+ bot.action(claudeAccountPickerPlan_1.accountOpenCodeUseCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
629
+ const provider = messageId === null ? undefined : providerSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId))?.[Number(ctx.match[1])];
630
+ const answerStale = async () => {
631
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
632
+ await rerenderPicker(key, messageId, isGeneral);
633
+ };
634
+ if (messageId === null || provider === undefined) {
635
+ await answerStale();
636
+ return;
637
+ }
638
+ if (isGeneral) {
639
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('limits.openCodeInTopicOnly'));
640
+ return;
641
+ }
642
+ if (getState().getBinding(key) === null) {
643
+ await answerWithWholeText(ctx, key, (0, i18n_1.t)('thread.bind_required'));
644
+ return;
645
+ }
646
+ const models = (0, fallbackChain_1.listOpenCodeModelsOfProvider)(await listOpenCodeModels(), provider);
647
+ await (0, modelPage_1.handleProviderTap)({
648
+ ...buildPickDeps(ctx, key, messageId, isGeneral),
649
+ providerId: provider,
650
+ models,
651
+ rung: getTopicRun(key).rung,
652
+ showPage: () => showProviderModelPage(key, messageId, provider, models, 0),
653
+ answerStale,
654
+ });
655
+ }));
656
+ bot.action(claudeAccountPickerPlan_1.accountOpenCodePageCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
657
+ const page = messageId === null ? undefined : pageSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId));
658
+ if (messageId === null || page === undefined) {
659
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
660
+ await rerenderPicker(key, messageId, isGeneral);
661
+ return;
662
+ }
663
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
664
+ await showProviderModelPage(key, messageId, page.providerId, page.models, Number(ctx.match[1]));
665
+ }));
666
+ // A model on the open page: the switch. A refusal stays on the page; anything else returns to the screen, its โœ“ moved.
667
+ bot.action(claudeAccountPickerPlan_1.accountOpenCodeModelCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
668
+ const page = messageId === null ? undefined : pageSnapshots.get((0, callbackMessage_1.buildMessageSnapshotKey)(key, messageId));
669
+ const modelId = page?.models[Number(ctx.match[1])];
670
+ if (messageId === null || page === undefined || modelId === undefined) {
671
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('accounts.stale'));
672
+ await rerenderPicker(key, messageId, isGeneral);
673
+ return;
674
+ }
675
+ await (0, modelPage_1.handleModelPick)(buildPickDeps(ctx, key, messageId, isGeneral), { backend: 'opencode', providerId: page.providerId, modelId });
676
+ }));
524
677
  bot.action(claudeAccountPickerPlan_1.accountLoginMenuCallback, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
525
- await ctx.answerCbQuery();
678
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
526
679
  if (messageId !== null)
527
680
  await editThreadMessage(key, messageId, (0, i18n_1.t)('account.pickLogin'), buildProfileChoiceKeyboard(claudeAccountPickerPlan_1.buildAccountLoginCallback, true));
528
681
  }));
529
682
  bot.action(claudeAccountPickerPlan_1.accountRemoveMenuCallback, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
530
- await ctx.answerCbQuery();
683
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
531
684
  if (Object.keys(getState().getClaudeProfileRecords()).length === 0) {
532
685
  await replyToThread(key, (0, i18n_1.t)('account.nothingToRemove'));
533
686
  return;
@@ -536,16 +689,16 @@ function createClaudeAccounts(ports) {
536
689
  await editThreadMessage(key, messageId, (0, i18n_1.t)('account.pickRemove'), buildProfileChoiceKeyboard(claudeAccountPickerPlan_1.buildAccountRemoveCallback, false));
537
690
  }));
538
691
  bot.action(claudeAccountPickerPlan_1.accountBackCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
539
- await ctx.answerCbQuery();
692
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
540
693
  await rerenderPicker(key, messageId, isGeneral);
541
694
  }));
542
695
  bot.action(claudeAccountPickerPlan_1.accountLoginCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
543
696
  const name = (0, claudeAccountPickerPlan_1.parseAccountCallbackName)(ctx.match[0], claudeAccountPickerPlan_1.accountLoginCallbackRe, { allowDefault: true });
544
697
  if (name === null) {
545
- await ctx.answerCbQuery();
698
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
546
699
  return;
547
700
  }
548
- await ctx.answerCbQuery();
701
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
549
702
  await rerenderPicker(key, messageId, isGeneral); // the sub-picker is consumed back into the main picker
550
703
  const message = await loginProfile(key, name);
551
704
  if (message)
@@ -554,10 +707,10 @@ function createClaudeAccounts(ports) {
554
707
  bot.action(claudeAccountPickerPlan_1.accountRemoveCallbackRe, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
555
708
  const name = (0, claudeAccountPickerPlan_1.parseAccountCallbackName)(ctx.match[0], claudeAccountPickerPlan_1.accountRemoveCallbackRe, { allowDefault: true });
556
709
  if (name === null || !checkIsKnownProfile(name)) {
557
- await ctx.answerCbQuery((0, i18n_1.t)('account.unknown', { name: name ?? '?' }));
710
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx, (0, i18n_1.t)('account.unknown', { name: name ?? '?' }));
558
711
  return;
559
712
  }
560
- await ctx.answerCbQuery();
713
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
561
714
  const confirmation = buildRemoveConfirmation(name);
562
715
  if (messageId !== null)
563
716
  await editThreadMessage(key, messageId, confirmation.text, confirmation.keyboard);
@@ -565,16 +718,16 @@ function createClaudeAccounts(ports) {
565
718
  bot.action(claudeAccountPickerPlan_1.accountRemoveConfirmCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
566
719
  const name = (0, claudeAccountPickerPlan_1.parseAccountCallbackName)(ctx.match[0], claudeAccountPickerPlan_1.accountRemoveConfirmCallbackRe, { allowDefault: true });
567
720
  if (name === null) {
568
- await ctx.answerCbQuery();
721
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
569
722
  return;
570
723
  }
571
724
  const result = await removeProfile(name);
572
- await answerTap(ctx, key, result);
725
+ await answerWithWholeText(ctx, key, result);
573
726
  await rerenderPicker(key, messageId, isGeneral);
574
727
  }));
575
728
  for (const [callback, enabled] of [[claudeAccountPickerPlan_1.accountAutoSwitchOnCallback, true], [claudeAccountPickerPlan_1.accountAutoSwitchOffCallback, false]]) {
576
729
  bot.action(callback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
577
- await answerTap(ctx, key, await applyAutoSwitch(key, isGeneral, enabled));
730
+ await answerWithWholeText(ctx, key, await applyAutoSwitch(key, isGeneral, enabled));
578
731
  await rerenderPicker(key, messageId, isGeneral);
579
732
  }));
580
733
  }
@@ -585,6 +738,7 @@ function createClaudeAccounts(ports) {
585
738
  checkIsAwaitingProfileName,
586
739
  cancelProfileNameCapture,
587
740
  submitProfileName,
741
+ pickClaudeAccount: applyPick,
588
742
  onClaudeProfileAuthStatus,
589
743
  };
590
744
  }