@0xmaxma/claude-gateway 1.8.13 → 2.0.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 (594) hide show
  1. package/README.md +28 -1605
  2. package/config.template.json +7 -1
  3. package/dist/agent/runner.d.ts +394 -0
  4. package/dist/agent/runner.d.ts.map +1 -1
  5. package/dist/agent/runner.js +867 -38
  6. package/dist/agent/runner.js.map +1 -1
  7. package/dist/agent/skill-learning/index.d.ts +6 -4
  8. package/dist/agent/skill-learning/index.d.ts.map +1 -1
  9. package/dist/agent/skill-learning/index.js +3 -3
  10. package/dist/agent/skill-learning/index.js.map +1 -1
  11. package/dist/agent/skill-learning/notifier.d.ts +3 -4
  12. package/dist/agent/skill-learning/notifier.d.ts.map +1 -1
  13. package/dist/agent/skill-learning/notifier.js +15 -13
  14. package/dist/agent/skill-learning/notifier.js.map +1 -1
  15. package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
  16. package/dist/agent/skill-learning/reviewer.js +3 -1
  17. package/dist/agent/skill-learning/reviewer.js.map +1 -1
  18. package/dist/api/connectors-router.d.ts.map +1 -1
  19. package/dist/api/connectors-router.js +110 -4
  20. package/dist/api/connectors-router.js.map +1 -1
  21. package/dist/api/gateway-router.d.ts +1 -0
  22. package/dist/api/gateway-router.d.ts.map +1 -1
  23. package/dist/api/gateway-router.js +13 -1
  24. package/dist/api/gateway-router.js.map +1 -1
  25. package/dist/api/line-webhook-router.d.ts +2 -2
  26. package/dist/api/line-webhook-router.d.ts.map +1 -1
  27. package/dist/api/line-webhook-router.js +264 -230
  28. package/dist/api/line-webhook-router.js.map +1 -1
  29. package/dist/api/router.d.ts.map +1 -1
  30. package/dist/api/router.js +372 -11
  31. package/dist/api/router.js.map +1 -1
  32. package/dist/api/share-router.d.ts.map +1 -1
  33. package/dist/api/share-router.js +29 -10
  34. package/dist/api/share-router.js.map +1 -1
  35. package/dist/api/slack-client.d.ts +2 -0
  36. package/dist/api/slack-client.d.ts.map +1 -1
  37. package/dist/api/slack-client.js +6 -2
  38. package/dist/api/slack-client.js.map +1 -1
  39. package/dist/api/slack-webhook-router.d.ts +3 -0
  40. package/dist/api/slack-webhook-router.d.ts.map +1 -1
  41. package/dist/api/slack-webhook-router.js +226 -131
  42. package/dist/api/slack-webhook-router.js.map +1 -1
  43. package/dist/api/voice-router.d.ts +21 -0
  44. package/dist/api/voice-router.d.ts.map +1 -0
  45. package/dist/api/voice-router.js +372 -0
  46. package/dist/api/voice-router.js.map +1 -0
  47. package/dist/api/voice-settings-router.d.ts +5 -0
  48. package/dist/api/voice-settings-router.d.ts.map +1 -0
  49. package/dist/api/voice-settings-router.js +318 -0
  50. package/dist/api/voice-settings-router.js.map +1 -0
  51. package/dist/api/wechat-access.d.ts +45 -0
  52. package/dist/api/wechat-access.d.ts.map +1 -0
  53. package/dist/api/wechat-access.js +48 -0
  54. package/dist/api/wechat-access.js.map +1 -0
  55. package/dist/apps/agent-container-migration.d.ts +11 -0
  56. package/dist/apps/agent-container-migration.d.ts.map +1 -0
  57. package/dist/apps/agent-container-migration.js +85 -0
  58. package/dist/apps/agent-container-migration.js.map +1 -0
  59. package/dist/apps/installer.d.ts +1 -1
  60. package/dist/apps/installer.d.ts.map +1 -1
  61. package/dist/apps/installer.js +2 -1
  62. package/dist/apps/installer.js.map +1 -1
  63. package/dist/cli/commands/tasks.d.ts +5 -0
  64. package/dist/cli/commands/tasks.d.ts.map +1 -0
  65. package/dist/cli/commands/tasks.js +92 -0
  66. package/dist/cli/commands/tasks.js.map +1 -0
  67. package/dist/cli/http-client.d.ts +2 -0
  68. package/dist/cli/http-client.d.ts.map +1 -1
  69. package/dist/cli/http-client.js +8 -3
  70. package/dist/cli/http-client.js.map +1 -1
  71. package/dist/cli/index.d.ts.map +1 -1
  72. package/dist/cli/index.js +5 -1
  73. package/dist/cli/index.js.map +1 -1
  74. package/dist/config/config-write-lock.d.ts +2 -0
  75. package/dist/config/config-write-lock.d.ts.map +1 -1
  76. package/dist/config/config-write-lock.js +5 -0
  77. package/dist/config/config-write-lock.js.map +1 -1
  78. package/dist/config/loader.d.ts.map +1 -1
  79. package/dist/config/loader.js +87 -2
  80. package/dist/config/loader.js.map +1 -1
  81. package/dist/config/migrator.d.ts.map +1 -1
  82. package/dist/config/migrator.js +12 -1
  83. package/dist/config/migrator.js.map +1 -1
  84. package/dist/config/watcher.d.ts.map +1 -1
  85. package/dist/config/watcher.js +6 -0
  86. package/dist/config/watcher.js.map +1 -1
  87. package/dist/connectors/resolve.d.ts.map +1 -1
  88. package/dist/connectors/resolve.js +3 -1
  89. package/dist/connectors/resolve.js.map +1 -1
  90. package/dist/connectors/types.d.ts +8 -0
  91. package/dist/connectors/types.d.ts.map +1 -1
  92. package/dist/cron/manager.d.ts.map +1 -1
  93. package/dist/cron/manager.js +21 -2
  94. package/dist/cron/manager.js.map +1 -1
  95. package/dist/discord/receiver.d.ts +2 -1
  96. package/dist/discord/receiver.d.ts.map +1 -1
  97. package/dist/discord/receiver.js +5 -1
  98. package/dist/discord/receiver.js.map +1 -1
  99. package/dist/history/db.d.ts +3 -0
  100. package/dist/history/db.d.ts.map +1 -1
  101. package/dist/history/db.js +21 -1
  102. package/dist/history/db.js.map +1 -1
  103. package/dist/history/types.d.ts +12 -1
  104. package/dist/history/types.d.ts.map +1 -1
  105. package/dist/history/types.js +10 -1
  106. package/dist/history/types.js.map +1 -1
  107. package/dist/index.js +48 -9
  108. package/dist/index.js.map +1 -1
  109. package/dist/orchestration/bounded-queue.d.ts +15 -0
  110. package/dist/orchestration/bounded-queue.d.ts.map +1 -0
  111. package/dist/orchestration/bounded-queue.js +58 -0
  112. package/dist/orchestration/bounded-queue.js.map +1 -0
  113. package/dist/orchestration/bridge.d.ts +55 -0
  114. package/dist/orchestration/bridge.d.ts.map +1 -0
  115. package/dist/orchestration/bridge.js +234 -0
  116. package/dist/orchestration/bridge.js.map +1 -0
  117. package/dist/orchestration/browser-routing.d.ts +7 -0
  118. package/dist/orchestration/browser-routing.d.ts.map +1 -0
  119. package/dist/orchestration/browser-routing.js +22 -0
  120. package/dist/orchestration/browser-routing.js.map +1 -0
  121. package/dist/orchestration/capabilities.d.ts +49 -0
  122. package/dist/orchestration/capabilities.d.ts.map +1 -0
  123. package/dist/orchestration/capabilities.js +417 -0
  124. package/dist/orchestration/capabilities.js.map +1 -0
  125. package/dist/orchestration/capacity.d.ts +14 -0
  126. package/dist/orchestration/capacity.d.ts.map +1 -0
  127. package/dist/orchestration/capacity.js +52 -0
  128. package/dist/orchestration/capacity.js.map +1 -0
  129. package/dist/orchestration/channel-activity.d.ts +21 -0
  130. package/dist/orchestration/channel-activity.d.ts.map +1 -0
  131. package/dist/orchestration/channel-activity.js +72 -0
  132. package/dist/orchestration/channel-activity.js.map +1 -0
  133. package/dist/orchestration/channel-controls.d.ts +32 -0
  134. package/dist/orchestration/channel-controls.d.ts.map +1 -0
  135. package/dist/orchestration/channel-controls.js +105 -0
  136. package/dist/orchestration/channel-controls.js.map +1 -0
  137. package/dist/orchestration/channel-input-media.d.ts +11 -0
  138. package/dist/orchestration/channel-input-media.d.ts.map +1 -0
  139. package/dist/orchestration/channel-input-media.js +54 -0
  140. package/dist/orchestration/channel-input-media.js.map +1 -0
  141. package/dist/orchestration/channel-media.d.ts +4 -0
  142. package/dist/orchestration/channel-media.d.ts.map +1 -0
  143. package/dist/orchestration/channel-media.js +70 -0
  144. package/dist/orchestration/channel-media.js.map +1 -0
  145. package/dist/orchestration/channel-speech.d.ts +7 -0
  146. package/dist/orchestration/channel-speech.d.ts.map +1 -0
  147. package/dist/orchestration/channel-speech.js +65 -0
  148. package/dist/orchestration/channel-speech.js.map +1 -0
  149. package/dist/orchestration/cli-skills.d.ts +12 -0
  150. package/dist/orchestration/cli-skills.d.ts.map +1 -0
  151. package/dist/orchestration/cli-skills.js +132 -0
  152. package/dist/orchestration/cli-skills.js.map +1 -0
  153. package/dist/orchestration/config.d.ts +256 -0
  154. package/dist/orchestration/config.d.ts.map +1 -0
  155. package/dist/orchestration/config.js +110 -0
  156. package/dist/orchestration/config.js.map +1 -0
  157. package/dist/orchestration/container.d.ts +21 -0
  158. package/dist/orchestration/container.d.ts.map +1 -0
  159. package/dist/orchestration/container.js +195 -0
  160. package/dist/orchestration/container.js.map +1 -0
  161. package/dist/orchestration/control-delivery.d.ts +5 -0
  162. package/dist/orchestration/control-delivery.d.ts.map +1 -0
  163. package/dist/orchestration/control-delivery.js +50 -0
  164. package/dist/orchestration/control-delivery.js.map +1 -0
  165. package/dist/orchestration/conversation-intake.d.ts +32 -0
  166. package/dist/orchestration/conversation-intake.d.ts.map +1 -0
  167. package/dist/orchestration/conversation-intake.js +92 -0
  168. package/dist/orchestration/conversation-intake.js.map +1 -0
  169. package/dist/orchestration/decisions.d.ts +27 -0
  170. package/dist/orchestration/decisions.d.ts.map +1 -0
  171. package/dist/orchestration/decisions.js +148 -0
  172. package/dist/orchestration/decisions.js.map +1 -0
  173. package/dist/orchestration/delivery.d.ts +30 -0
  174. package/dist/orchestration/delivery.d.ts.map +1 -0
  175. package/dist/orchestration/delivery.js +219 -0
  176. package/dist/orchestration/delivery.js.map +1 -0
  177. package/dist/orchestration/display-stream.d.ts +4 -0
  178. package/dist/orchestration/display-stream.d.ts.map +1 -0
  179. package/dist/orchestration/display-stream.js +77 -0
  180. package/dist/orchestration/display-stream.js.map +1 -0
  181. package/dist/orchestration/events.d.ts +30 -0
  182. package/dist/orchestration/events.d.ts.map +1 -0
  183. package/dist/orchestration/events.js +65 -0
  184. package/dist/orchestration/events.js.map +1 -0
  185. package/dist/orchestration/execution-observation.d.ts +38 -0
  186. package/dist/orchestration/execution-observation.d.ts.map +1 -0
  187. package/dist/orchestration/execution-observation.js +25 -0
  188. package/dist/orchestration/execution-observation.js.map +1 -0
  189. package/dist/orchestration/file-delivery.d.ts +16 -0
  190. package/dist/orchestration/file-delivery.d.ts.map +1 -0
  191. package/dist/orchestration/file-delivery.js +139 -0
  192. package/dist/orchestration/file-delivery.js.map +1 -0
  193. package/dist/orchestration/gateway-config.d.ts +17 -0
  194. package/dist/orchestration/gateway-config.d.ts.map +1 -0
  195. package/dist/orchestration/gateway-config.js +128 -0
  196. package/dist/orchestration/gateway-config.js.map +1 -0
  197. package/dist/orchestration/history.d.ts +13 -0
  198. package/dist/orchestration/history.d.ts.map +1 -0
  199. package/dist/orchestration/history.js +48 -0
  200. package/dist/orchestration/history.js.map +1 -0
  201. package/dist/orchestration/identity.d.ts +6 -0
  202. package/dist/orchestration/identity.d.ts.map +1 -0
  203. package/dist/orchestration/identity.js +13 -0
  204. package/dist/orchestration/identity.js.map +1 -0
  205. package/dist/orchestration/inference-errors.d.ts +3 -0
  206. package/dist/orchestration/inference-errors.d.ts.map +1 -0
  207. package/dist/orchestration/inference-errors.js +16 -0
  208. package/dist/orchestration/inference-errors.js.map +1 -0
  209. package/dist/orchestration/input-images.d.ts +12 -0
  210. package/dist/orchestration/input-images.d.ts.map +1 -0
  211. package/dist/orchestration/input-images.js +66 -0
  212. package/dist/orchestration/input-images.js.map +1 -0
  213. package/dist/orchestration/instance-lock.d.ts +5 -0
  214. package/dist/orchestration/instance-lock.d.ts.map +1 -0
  215. package/dist/orchestration/instance-lock.js +33 -0
  216. package/dist/orchestration/instance-lock.js.map +1 -0
  217. package/dist/orchestration/line-loading.d.ts +21 -0
  218. package/dist/orchestration/line-loading.d.ts.map +1 -0
  219. package/dist/orchestration/line-loading.js +89 -0
  220. package/dist/orchestration/line-loading.js.map +1 -0
  221. package/dist/orchestration/media.d.ts +3 -0
  222. package/dist/orchestration/media.d.ts.map +1 -0
  223. package/dist/orchestration/media.js +46 -0
  224. package/dist/orchestration/media.js.map +1 -0
  225. package/dist/orchestration/migrations/schema.d.ts +3 -0
  226. package/dist/orchestration/migrations/schema.d.ts.map +1 -0
  227. package/dist/orchestration/migrations/schema.js +80 -0
  228. package/dist/orchestration/migrations/schema.js.map +1 -0
  229. package/dist/orchestration/notification-mailbox.d.ts +5 -0
  230. package/dist/orchestration/notification-mailbox.d.ts.map +1 -0
  231. package/dist/orchestration/notification-mailbox.js +29 -0
  232. package/dist/orchestration/notification-mailbox.js.map +1 -0
  233. package/dist/orchestration/process-activity.d.ts +23 -0
  234. package/dist/orchestration/process-activity.d.ts.map +1 -0
  235. package/dist/orchestration/process-activity.js +95 -0
  236. package/dist/orchestration/process-activity.js.map +1 -0
  237. package/dist/orchestration/process-supervisor.d.ts +15 -0
  238. package/dist/orchestration/process-supervisor.d.ts.map +1 -0
  239. package/dist/orchestration/process-supervisor.js +92 -0
  240. package/dist/orchestration/process-supervisor.js.map +1 -0
  241. package/dist/orchestration/process-turn.d.ts +35 -0
  242. package/dist/orchestration/process-turn.d.ts.map +1 -0
  243. package/dist/orchestration/process-turn.js +246 -0
  244. package/dist/orchestration/process-turn.js.map +1 -0
  245. package/dist/orchestration/progress-review.d.ts +28 -0
  246. package/dist/orchestration/progress-review.d.ts.map +1 -0
  247. package/dist/orchestration/progress-review.js +48 -0
  248. package/dist/orchestration/progress-review.js.map +1 -0
  249. package/dist/orchestration/recovery.d.ts +8 -0
  250. package/dist/orchestration/recovery.d.ts.map +1 -0
  251. package/dist/orchestration/recovery.js +35 -0
  252. package/dist/orchestration/recovery.js.map +1 -0
  253. package/dist/orchestration/reply-context.d.ts +7 -0
  254. package/dist/orchestration/reply-context.d.ts.map +1 -0
  255. package/dist/orchestration/reply-context.js +54 -0
  256. package/dist/orchestration/reply-context.js.map +1 -0
  257. package/dist/orchestration/runtime.d.ts +232 -0
  258. package/dist/orchestration/runtime.d.ts.map +1 -0
  259. package/dist/orchestration/runtime.js +962 -0
  260. package/dist/orchestration/runtime.js.map +1 -0
  261. package/dist/orchestration/skills.d.ts +17 -0
  262. package/dist/orchestration/skills.d.ts.map +1 -0
  263. package/dist/orchestration/skills.js +32 -0
  264. package/dist/orchestration/skills.js.map +1 -0
  265. package/dist/orchestration/source-policy.d.ts +6 -0
  266. package/dist/orchestration/source-policy.d.ts.map +1 -0
  267. package/dist/orchestration/source-policy.js +16 -0
  268. package/dist/orchestration/source-policy.js.map +1 -0
  269. package/dist/orchestration/speech.d.ts +24 -0
  270. package/dist/orchestration/speech.d.ts.map +1 -0
  271. package/dist/orchestration/speech.js +61 -0
  272. package/dist/orchestration/speech.js.map +1 -0
  273. package/dist/orchestration/stop-controls.d.ts +30 -0
  274. package/dist/orchestration/stop-controls.d.ts.map +1 -0
  275. package/dist/orchestration/stop-controls.js +79 -0
  276. package/dist/orchestration/stop-controls.js.map +1 -0
  277. package/dist/orchestration/store.d.ts +86 -0
  278. package/dist/orchestration/store.d.ts.map +1 -0
  279. package/dist/orchestration/store.js +275 -0
  280. package/dist/orchestration/store.js.map +1 -0
  281. package/dist/orchestration/task-controls.d.ts +130 -0
  282. package/dist/orchestration/task-controls.d.ts.map +1 -0
  283. package/dist/orchestration/task-controls.js +55 -0
  284. package/dist/orchestration/task-controls.js.map +1 -0
  285. package/dist/orchestration/task-files.d.ts +29 -0
  286. package/dist/orchestration/task-files.d.ts.map +1 -0
  287. package/dist/orchestration/task-files.js +200 -0
  288. package/dist/orchestration/task-files.js.map +1 -0
  289. package/dist/orchestration/task-report.d.ts +7 -0
  290. package/dist/orchestration/task-report.d.ts.map +1 -0
  291. package/dist/orchestration/task-report.js +33 -0
  292. package/dist/orchestration/task-report.js.map +1 -0
  293. package/dist/orchestration/tasks/checkpoint-hook.d.ts +19 -0
  294. package/dist/orchestration/tasks/checkpoint-hook.d.ts.map +1 -0
  295. package/dist/orchestration/tasks/checkpoint-hook.js +49 -0
  296. package/dist/orchestration/tasks/checkpoint-hook.js.map +1 -0
  297. package/dist/orchestration/tasks/cleanup.d.ts +18 -0
  298. package/dist/orchestration/tasks/cleanup.d.ts.map +1 -0
  299. package/dist/orchestration/tasks/cleanup.js +113 -0
  300. package/dist/orchestration/tasks/cleanup.js.map +1 -0
  301. package/dist/orchestration/tasks/driver.d.ts +23 -0
  302. package/dist/orchestration/tasks/driver.d.ts.map +1 -0
  303. package/dist/orchestration/tasks/driver.js +209 -0
  304. package/dist/orchestration/tasks/driver.js.map +1 -0
  305. package/dist/orchestration/tasks/failure.d.ts +5 -0
  306. package/dist/orchestration/tasks/failure.d.ts.map +1 -0
  307. package/dist/orchestration/tasks/failure.js +17 -0
  308. package/dist/orchestration/tasks/failure.js.map +1 -0
  309. package/dist/orchestration/tasks/pool.d.ts +18 -0
  310. package/dist/orchestration/tasks/pool.d.ts.map +1 -0
  311. package/dist/orchestration/tasks/pool.js +73 -0
  312. package/dist/orchestration/tasks/pool.js.map +1 -0
  313. package/dist/orchestration/tasks/scheduler.d.ts +38 -0
  314. package/dist/orchestration/tasks/scheduler.d.ts.map +1 -0
  315. package/dist/orchestration/tasks/scheduler.js +160 -0
  316. package/dist/orchestration/tasks/scheduler.js.map +1 -0
  317. package/dist/orchestration/tasks/service.d.ts +109 -0
  318. package/dist/orchestration/tasks/service.d.ts.map +1 -0
  319. package/dist/orchestration/tasks/service.js +697 -0
  320. package/dist/orchestration/tasks/service.js.map +1 -0
  321. package/dist/orchestration/tasks/shared-workspace.d.ts +25 -0
  322. package/dist/orchestration/tasks/shared-workspace.d.ts.map +1 -0
  323. package/dist/orchestration/tasks/shared-workspace.js +162 -0
  324. package/dist/orchestration/tasks/shared-workspace.js.map +1 -0
  325. package/dist/orchestration/tasks/task-directive.d.ts +6 -0
  326. package/dist/orchestration/tasks/task-directive.d.ts.map +1 -0
  327. package/dist/orchestration/tasks/task-directive.js +41 -0
  328. package/dist/orchestration/tasks/task-directive.js.map +1 -0
  329. package/dist/orchestration/tasks/timing.d.ts +15 -0
  330. package/dist/orchestration/tasks/timing.d.ts.map +1 -0
  331. package/dist/orchestration/tasks/timing.js +48 -0
  332. package/dist/orchestration/tasks/timing.js.map +1 -0
  333. package/dist/orchestration/tasks/tool-repetition.d.ts +3 -0
  334. package/dist/orchestration/tasks/tool-repetition.d.ts.map +1 -0
  335. package/dist/orchestration/tasks/tool-repetition.js +30 -0
  336. package/dist/orchestration/tasks/tool-repetition.js.map +1 -0
  337. package/dist/orchestration/tasks/workspace.d.ts +24 -0
  338. package/dist/orchestration/tasks/workspace.d.ts.map +1 -0
  339. package/dist/orchestration/tasks/workspace.js +147 -0
  340. package/dist/orchestration/tasks/workspace.js.map +1 -0
  341. package/dist/orchestration/telegram-speech.d.ts +17 -0
  342. package/dist/orchestration/telegram-speech.d.ts.map +1 -0
  343. package/dist/orchestration/telegram-speech.js +55 -0
  344. package/dist/orchestration/telegram-speech.js.map +1 -0
  345. package/dist/orchestration/telegram-tool-status.d.ts +23 -0
  346. package/dist/orchestration/telegram-tool-status.d.ts.map +1 -0
  347. package/dist/orchestration/telegram-tool-status.js +207 -0
  348. package/dist/orchestration/telegram-tool-status.js.map +1 -0
  349. package/dist/orchestration/telegram-voices.d.ts +42 -0
  350. package/dist/orchestration/telegram-voices.d.ts.map +1 -0
  351. package/dist/orchestration/telegram-voices.js +70 -0
  352. package/dist/orchestration/telegram-voices.js.map +1 -0
  353. package/dist/orchestration/tool-activity.d.ts +10 -0
  354. package/dist/orchestration/tool-activity.d.ts.map +1 -0
  355. package/dist/orchestration/tool-activity.js +38 -0
  356. package/dist/orchestration/tool-activity.js.map +1 -0
  357. package/dist/orchestration/types.d.ts +177 -0
  358. package/dist/orchestration/types.d.ts.map +1 -0
  359. package/dist/orchestration/types.js +13 -0
  360. package/dist/orchestration/types.js.map +1 -0
  361. package/dist/orchestration/voice-reply-policy.d.ts +8 -0
  362. package/dist/orchestration/voice-reply-policy.d.ts.map +1 -0
  363. package/dist/orchestration/voice-reply-policy.js +46 -0
  364. package/dist/orchestration/voice-reply-policy.js.map +1 -0
  365. package/dist/orchestration/worker-shares.d.ts +8 -0
  366. package/dist/orchestration/worker-shares.d.ts.map +1 -0
  367. package/dist/orchestration/worker-shares.js +59 -0
  368. package/dist/orchestration/worker-shares.js.map +1 -0
  369. package/dist/orchestration/workflow.d.ts +121 -0
  370. package/dist/orchestration/workflow.d.ts.map +1 -0
  371. package/dist/orchestration/workflow.js +69 -0
  372. package/dist/orchestration/workflow.js.map +1 -0
  373. package/dist/session/input-image.d.ts +10 -0
  374. package/dist/session/input-image.d.ts.map +1 -0
  375. package/dist/session/input-image.js +3 -0
  376. package/dist/session/input-image.js.map +1 -0
  377. package/dist/session/process.d.ts +15 -3
  378. package/dist/session/process.d.ts.map +1 -1
  379. package/dist/session/process.js +144 -35
  380. package/dist/session/process.js.map +1 -1
  381. package/dist/session/runtime-profile.d.ts +36 -0
  382. package/dist/session/runtime-profile.d.ts.map +1 -0
  383. package/dist/session/runtime-profile.js +63 -0
  384. package/dist/session/runtime-profile.js.map +1 -0
  385. package/dist/session/store.d.ts.map +1 -1
  386. package/dist/session/store.js +23 -1
  387. package/dist/session/store.js.map +1 -1
  388. package/dist/share/share-store.d.ts +3 -1
  389. package/dist/share/share-store.d.ts.map +1 -1
  390. package/dist/share/share-store.js +13 -2
  391. package/dist/share/share-store.js.map +1 -1
  392. package/dist/shared/line-request-order.d.ts +2 -0
  393. package/dist/shared/line-request-order.d.ts.map +1 -0
  394. package/dist/shared/line-request-order.js +16 -0
  395. package/dist/shared/line-request-order.js.map +1 -0
  396. package/dist/shared/task-elapsed.d.ts +3 -0
  397. package/dist/shared/task-elapsed.d.ts.map +1 -0
  398. package/dist/shared/task-elapsed.js +12 -0
  399. package/dist/shared/task-elapsed.js.map +1 -0
  400. package/dist/shared/task-updated-time.d.ts +3 -0
  401. package/dist/shared/task-updated-time.d.ts.map +1 -0
  402. package/dist/shared/task-updated-time.js +15 -0
  403. package/dist/shared/task-updated-time.js.map +1 -0
  404. package/dist/skills/loader.d.ts +2 -0
  405. package/dist/skills/loader.d.ts.map +1 -1
  406. package/dist/skills/loader.js.map +1 -1
  407. package/dist/telegram/chunks.d.ts +19 -0
  408. package/dist/telegram/chunks.d.ts.map +1 -0
  409. package/dist/telegram/chunks.js +129 -0
  410. package/dist/telegram/chunks.js.map +1 -0
  411. package/dist/telegram/markdown.d.ts.map +1 -1
  412. package/dist/telegram/markdown.js +18 -3
  413. package/dist/telegram/markdown.js.map +1 -1
  414. package/dist/telegram/receiver.d.ts +2 -1
  415. package/dist/telegram/receiver.d.ts.map +1 -1
  416. package/dist/telegram/receiver.js +6 -1
  417. package/dist/telegram/receiver.js.map +1 -1
  418. package/dist/types.d.ts +54 -0
  419. package/dist/types.d.ts.map +1 -1
  420. package/dist/ui/web-ui.d.ts.map +1 -1
  421. package/dist/ui/web-ui.js +31 -5
  422. package/dist/ui/web-ui.js.map +1 -1
  423. package/dist/voice/diagnostics.d.ts +29 -0
  424. package/dist/voice/diagnostics.d.ts.map +1 -0
  425. package/dist/voice/diagnostics.js +37 -0
  426. package/dist/voice/diagnostics.js.map +1 -0
  427. package/dist/voice/errors.d.ts +19 -0
  428. package/dist/voice/errors.d.ts.map +1 -0
  429. package/dist/voice/errors.js +133 -0
  430. package/dist/voice/errors.js.map +1 -0
  431. package/dist/voice/line-audio.d.ts +4 -0
  432. package/dist/voice/line-audio.d.ts.map +1 -0
  433. package/dist/voice/line-audio.js +27 -0
  434. package/dist/voice/line-audio.js.map +1 -0
  435. package/dist/voice/managed-quota.d.ts +5 -0
  436. package/dist/voice/managed-quota.d.ts.map +1 -0
  437. package/dist/voice/managed-quota.js +35 -0
  438. package/dist/voice/managed-quota.js.map +1 -0
  439. package/dist/voice/mp3.d.ts +3 -0
  440. package/dist/voice/mp3.d.ts.map +1 -0
  441. package/dist/voice/mp3.js +30 -0
  442. package/dist/voice/mp3.js.map +1 -0
  443. package/dist/voice/notes.d.ts +9 -0
  444. package/dist/voice/notes.d.ts.map +1 -0
  445. package/dist/voice/notes.js +68 -0
  446. package/dist/voice/notes.js.map +1 -0
  447. package/dist/voice/playback.d.ts +22 -0
  448. package/dist/voice/playback.d.ts.map +1 -0
  449. package/dist/voice/playback.js +52 -0
  450. package/dist/voice/playback.js.map +1 -0
  451. package/dist/voice/preview.d.ts +6 -0
  452. package/dist/voice/preview.d.ts.map +1 -0
  453. package/dist/voice/preview.js +44 -0
  454. package/dist/voice/preview.js.map +1 -0
  455. package/dist/voice/protocol.d.ts +10 -0
  456. package/dist/voice/protocol.d.ts.map +1 -0
  457. package/dist/voice/protocol.js +30 -0
  458. package/dist/voice/protocol.js.map +1 -0
  459. package/dist/voice/providers/cartesia-tts.d.ts +26 -0
  460. package/dist/voice/providers/cartesia-tts.d.ts.map +1 -0
  461. package/dist/voice/providers/cartesia-tts.js +95 -0
  462. package/dist/voice/providers/cartesia-tts.js.map +1 -0
  463. package/dist/voice/providers/deepgram-stt.d.ts +20 -0
  464. package/dist/voice/providers/deepgram-stt.d.ts.map +1 -0
  465. package/dist/voice/providers/deepgram-stt.js +66 -0
  466. package/dist/voice/providers/deepgram-stt.js.map +1 -0
  467. package/dist/voice/providers/elevenlabs-stt.d.ts +20 -0
  468. package/dist/voice/providers/elevenlabs-stt.d.ts.map +1 -0
  469. package/dist/voice/providers/elevenlabs-stt.js +92 -0
  470. package/dist/voice/providers/elevenlabs-stt.js.map +1 -0
  471. package/dist/voice/providers/elevenlabs-tts.d.ts +26 -0
  472. package/dist/voice/providers/elevenlabs-tts.d.ts.map +1 -0
  473. package/dist/voice/providers/elevenlabs-tts.js +82 -0
  474. package/dist/voice/providers/elevenlabs-tts.js.map +1 -0
  475. package/dist/voice/providers/encoded-audio-tts.d.ts +21 -0
  476. package/dist/voice/providers/encoded-audio-tts.d.ts.map +1 -0
  477. package/dist/voice/providers/encoded-audio-tts.js +175 -0
  478. package/dist/voice/providers/encoded-audio-tts.js.map +1 -0
  479. package/dist/voice/providers/fake.d.ts +39 -0
  480. package/dist/voice/providers/fake.d.ts.map +1 -0
  481. package/dist/voice/providers/fake.js +41 -0
  482. package/dist/voice/providers/fake.js.map +1 -0
  483. package/dist/voice/providers/gemini-live-stt.d.ts +21 -0
  484. package/dist/voice/providers/gemini-live-stt.d.ts.map +1 -0
  485. package/dist/voice/providers/gemini-live-stt.js +158 -0
  486. package/dist/voice/providers/gemini-live-stt.js.map +1 -0
  487. package/dist/voice/providers/gemini.d.ts +60 -0
  488. package/dist/voice/providers/gemini.d.ts.map +1 -0
  489. package/dist/voice/providers/gemini.js +124 -0
  490. package/dist/voice/providers/gemini.js.map +1 -0
  491. package/dist/voice/providers/model-ref.d.ts +9 -0
  492. package/dist/voice/providers/model-ref.d.ts.map +1 -0
  493. package/dist/voice/providers/model-ref.js +31 -0
  494. package/dist/voice/providers/model-ref.js.map +1 -0
  495. package/dist/voice/providers/openrouter.d.ts +39 -0
  496. package/dist/voice/providers/openrouter.d.ts.map +1 -0
  497. package/dist/voice/providers/openrouter.js +154 -0
  498. package/dist/voice/providers/openrouter.js.map +1 -0
  499. package/dist/voice/providers/paxalabs-stt.d.ts +12 -0
  500. package/dist/voice/providers/paxalabs-stt.d.ts.map +1 -0
  501. package/dist/voice/providers/paxalabs-stt.js +41 -0
  502. package/dist/voice/providers/paxalabs-stt.js.map +1 -0
  503. package/dist/voice/providers/paxalabs-tts.d.ts +23 -0
  504. package/dist/voice/providers/paxalabs-tts.d.ts.map +1 -0
  505. package/dist/voice/providers/paxalabs-tts.js +80 -0
  506. package/dist/voice/providers/paxalabs-tts.js.map +1 -0
  507. package/dist/voice/providers/recorded-stt.d.ts +34 -0
  508. package/dist/voice/providers/recorded-stt.d.ts.map +1 -0
  509. package/dist/voice/providers/recorded-stt.js +59 -0
  510. package/dist/voice/providers/recorded-stt.js.map +1 -0
  511. package/dist/voice/providers/registry.d.ts +10 -0
  512. package/dist/voice/providers/registry.d.ts.map +1 -0
  513. package/dist/voice/providers/registry.js +77 -0
  514. package/dist/voice/providers/registry.js.map +1 -0
  515. package/dist/voice/providers/socket.d.ts +10 -0
  516. package/dist/voice/providers/socket.d.ts.map +1 -0
  517. package/dist/voice/providers/socket.js +91 -0
  518. package/dist/voice/providers/socket.js.map +1 -0
  519. package/dist/voice/providers/upstream.d.ts +9 -0
  520. package/dist/voice/providers/upstream.d.ts.map +1 -0
  521. package/dist/voice/providers/upstream.js +51 -0
  522. package/dist/voice/providers/upstream.js.map +1 -0
  523. package/dist/voice/providers/voice-catalog.d.ts +17 -0
  524. package/dist/voice/providers/voice-catalog.d.ts.map +1 -0
  525. package/dist/voice/providers/voice-catalog.js +167 -0
  526. package/dist/voice/providers/voice-catalog.js.map +1 -0
  527. package/dist/voice/queue.d.ts +2 -0
  528. package/dist/voice/queue.d.ts.map +1 -0
  529. package/dist/voice/queue.js +6 -0
  530. package/dist/voice/queue.js.map +1 -0
  531. package/dist/voice/session.d.ts +86 -0
  532. package/dist/voice/session.d.ts.map +1 -0
  533. package/dist/voice/session.js +464 -0
  534. package/dist/voice/session.js.map +1 -0
  535. package/dist/voice/turn-manager.d.ts +27 -0
  536. package/dist/voice/turn-manager.d.ts.map +1 -0
  537. package/dist/voice/turn-manager.js +75 -0
  538. package/dist/voice/turn-manager.js.map +1 -0
  539. package/dist/voice/types.d.ts +78 -0
  540. package/dist/voice/types.d.ts.map +1 -0
  541. package/dist/voice/types.js +18 -0
  542. package/dist/voice/types.js.map +1 -0
  543. package/dist/voice/wav.d.ts +2 -0
  544. package/dist/voice/wav.d.ts.map +1 -0
  545. package/dist/voice/wav.js +21 -0
  546. package/dist/voice/wav.js.map +1 -0
  547. package/dist/wechat/ilink-client.d.ts +207 -0
  548. package/dist/wechat/ilink-client.d.ts.map +1 -0
  549. package/dist/wechat/ilink-client.js +660 -0
  550. package/dist/wechat/ilink-client.js.map +1 -0
  551. package/dist/wechat/manager.d.ts +93 -0
  552. package/dist/wechat/manager.d.ts.map +1 -0
  553. package/dist/wechat/manager.js +409 -0
  554. package/dist/wechat/manager.js.map +1 -0
  555. package/dist/whatsapp/manager.d.ts.map +1 -1
  556. package/dist/whatsapp/manager.js +51 -1
  557. package/dist/whatsapp/manager.js.map +1 -1
  558. package/mcp/capability-catalog.ts +206 -0
  559. package/mcp/modules.ts +56 -0
  560. package/mcp/server.ts +18 -35
  561. package/mcp/tools/browser/module.ts +1 -1
  562. package/mcp/tools/browser/skills/open-browser/SKILL.md +5 -3
  563. package/mcp/tools/discord/commands.ts +17 -1
  564. package/mcp/tools/discord/inbound.ts +11 -0
  565. package/mcp/tools/discord/module.ts +42 -2
  566. package/mcp/tools/discord/receiver-server.ts +10 -0
  567. package/mcp/tools/discord/types.ts +4 -1
  568. package/mcp/tools/image/module.ts +3 -1
  569. package/mcp/tools/receiver-spool.test.ts +28 -0
  570. package/mcp/tools/receiver-spool.ts +56 -0
  571. package/mcp/tools/share-file/module.ts +9 -1
  572. package/mcp/tools/shared/share-client.ts +19 -0
  573. package/mcp/tools/tasks/module.ts +35 -0
  574. package/mcp/tools/telegram/media-group.ts +21 -0
  575. package/mcp/tools/telegram/receiver-server.ts +232 -11
  576. package/mcp/tools/telegram/selection-confirmation.ts +20 -0
  577. package/mcp/tools/telegram/task-browser.ts +117 -0
  578. package/mcp/tools/telegram/task-detail.ts +36 -0
  579. package/mcp/tools/telegram/task-elapsed.ts +1 -0
  580. package/mcp/tools/telegram/typing.ts +6 -116
  581. package/mcp/tools/video/module.ts +1 -1
  582. package/mcp/tools/wechat/client.ts +49 -0
  583. package/mcp/tools/wechat/module.ts +88 -0
  584. package/mcp/types.ts +5 -0
  585. package/package.json +2 -1
  586. package/resource/claude_gateway.jpeg +0 -0
  587. package/scripts/orchestration/bench-store.cjs +30 -0
  588. package/scripts/orchestration/inspect.cjs +16 -0
  589. package/scripts/orchestration/probe-profile.cjs +67 -0
  590. package/scripts/orchestration/reconcile.cjs +21 -0
  591. package/scripts/orchestration/smoke-elevenlabs.cjs +90 -0
  592. package/scripts/orchestration/smoke-media.cjs +70 -0
  593. package/scripts/orchestration/smoke-text.cjs +101 -0
  594. package/scripts/orchestration/smoke-vision.cjs +49 -0
@@ -25,6 +25,13 @@ import { z } from 'zod'
25
25
  import { Bot, GrammyError, InlineKeyboard, InputFile, type Context } from 'grammy'
26
26
  import type { ReactionTypeEmoji } from 'grammy/types'
27
27
  import { randomBytes } from 'crypto'
28
+ import { confirmSelection, voiceModeConfirmation } from './selection-confirmation'
29
+ import { LiveTaskBrowser, type TaskBrowserMessage } from './task-browser'
30
+ import { ReceiverSpool } from '../receiver-spool'
31
+ const orchestrationSpool = process.env.GATEWAY_ORCHESTRATION_INGRESS_DIR && process.env.CLAUDE_CHANNEL_CALLBACK
32
+ ? new ReceiverSpool(process.env.GATEWAY_ORCHESTRATION_INGRESS_DIR, process.env.CLAUDE_CHANNEL_CALLBACK, fetch, Math.max(250,Math.min(10000,Number(process.env.GATEWAY_MEDIA_GROUP_WAIT_MS)||2000))) : undefined
33
+ const ORCHESTRATION_ENABLED = process.env.GATEWAY_ORCHESTRATION_ENABLED === 'true' || Boolean(orchestrationSpool)
34
+ const INTERACTIVE_CLI_ENABLED = process.env.GATEWAY_INTERACTIVE_CLI_ENABLED === 'true' && !ORCHESTRATION_ENABLED
28
35
  import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync, existsSync } from 'fs'
29
36
  import { homedir } from 'os'
30
37
  import { join, extname, sep } from 'path'
@@ -35,6 +42,7 @@ import { extractRepliedAttachment, safeName } from './reply-attachment'
35
42
  // so a src/ import crashes this bun-run receiver on installed packages (the bug
36
43
  // that silenced every bot on systemd installs). Enforced by
37
44
  // tests/unit/mcp-no-src-imports.test.ts.
45
+ import { formatTaskDetail, type TaskDetail } from './task-detail'
38
46
  import { formatTurnIncident, type TurnIncident } from '../../../dist/agent/turn-trace.js'
39
47
  import { createIncidentStore } from '../../../dist/agent/incident-store.js'
40
48
  import type { RecoveryOutcome } from '../../../dist/agent/incident.js'
@@ -214,6 +222,7 @@ const typingManager = createWorkingStateManager(
214
222
  )
215
223
  }
216
224
  },
225
+ ORCHESTRATION_ENABLED ? 'orchestration' : 'legacy',
217
226
  )
218
227
 
219
228
  // Stages whose recovery must run in the runner process (live session control):
@@ -863,17 +872,18 @@ const RECEIVER_MODE = process.env.TELEGRAM_RECEIVER_MODE === 'true'
863
872
  import { parseMenuFileContent } from './menu-parser'
864
873
 
865
874
  const BOT_COMMANDS = [
875
+ ...(ORCHESTRATION_ENABLED ? [{ command: 'tasks', description: 'View pending tasks in this chat' }, { command: 'voice', description: 'Turn automatic voice replies on or off' }, { command: 'voices', description: 'Choose the agent voice' }] : []),
866
876
  { command: 'session', description: 'Show current session info' },
867
877
  { command: 'sessions', description: 'Manage conversation sessions' },
868
878
  { command: 'new', description: 'Create a new session' },
869
879
  { command: 'rename', description: 'Rename current session' },
870
880
  { command: 'clear', description: 'Clear current session history' },
871
881
  { command: 'compact', description: 'Summarize and compress session history' },
872
- { command: 'stop', description: 'Interrupt the agent and stop current work' },
882
+ { command: 'stop', description: ORCHESTRATION_ENABLED ? 'Stop the agent reply and choose a task to cancel' : 'Interrupt the agent and stop current work' },
873
883
  { command: 'restart', description: 'Graceful restart session' },
874
884
  { command: 'model', description: 'Show current AI model' },
875
885
  { command: 'models', description: 'Switch AI model' },
876
- { command: 'cli', description: 'Open the live terminal viewer' },
886
+ ...(INTERACTIVE_CLI_ENABLED ? [{ command: 'cli', description: 'Open the live terminal viewer' }] : []),
877
887
  { command: 'start', description: 'Welcome and setup guide' },
878
888
  { command: 'status', description: 'Check your pairing status' },
879
889
  { command: 'help', description: 'What this bot can do' },
@@ -1023,7 +1033,7 @@ async function handleInbound(
1023
1033
  // Download photo from replied-to message if present
1024
1034
  const replyMsg = ctx.message?.reply_to_message
1025
1035
  let repliedImagePath: string | undefined
1026
- if (replyMsg?.photo) {
1036
+ if (replyMsg?.photo && !orchestrationSpool) {
1027
1037
  try {
1028
1038
  const largest = replyMsg.photo[replyMsg.photo.length - 1]!
1029
1039
  const file = await bot.api.getFile(largest.file_id)
@@ -1054,7 +1064,9 @@ async function handleInbound(
1054
1064
  content: text,
1055
1065
  meta: {
1056
1066
  chat_id,
1067
+ ...(ctx.message?.media_group_id ? {media_group_id:ctx.message.media_group_id,media_caption:ctx.message.caption??''} : {}),
1057
1068
  ...(msgId != null ? { message_id: String(msgId) } : {}),
1069
+ ...(ctx.message?.message_thread_id != null ? { message_thread_id: String(ctx.message.message_thread_id) } : {}),
1058
1070
  user: from.username ?? String(from.id),
1059
1071
  user_id: String(from.id),
1060
1072
  // Human-readable name for the chat picker / history (first_name is always
@@ -1072,8 +1084,9 @@ async function handleInbound(
1072
1084
  ...(replyMsg ? {
1073
1085
  replied_message_id: String(replyMsg.message_id),
1074
1086
  replied_user: replyMsg.from?.username ?? String(replyMsg.from?.id ?? ''),
1075
- ...(replyMsg.text ? { replied_text: replyMsg.text } : {}),
1087
+ ...((replyMsg.text || replyMsg.caption) ? { replied_text: replyMsg.text || replyMsg.caption } : {}),
1076
1088
  ...(repliedImagePath ? { replied_image_path: repliedImagePath } : {}),
1089
+ ...(orchestrationSpool && replyMsg.photo ? {replied_attachment_file_id:replyMsg.photo[replyMsg.photo.length-1]!.file_id} : {}),
1077
1090
  ...(repliedAttachment ? {
1078
1091
  replied_attachment_kind: repliedAttachment.kind,
1079
1092
  replied_attachment_file_id: repliedAttachment.file_id,
@@ -1103,15 +1116,16 @@ async function handleInbound(
1103
1116
  // turns in --print --channels mode).
1104
1117
  const callbackUrl = process.env.CLAUDE_CHANNEL_CALLBACK
1105
1118
  if (callbackUrl) {
1106
- fetch(callbackUrl, {
1119
+ if (orchestrationSpool) orchestrationSpool.enqueue(channelParams)
1120
+ else fetch(callbackUrl, {
1107
1121
  method: 'POST',
1108
1122
  headers: { 'Content-Type': 'application/json' },
1109
1123
  body: JSON.stringify(channelParams),
1110
1124
  }).catch(err => {
1111
1125
  process.stderr.write(`telegram channel: callback POST failed: ${err}\n`)
1112
1126
  })
1113
- // Start typing indicator loop — only in receiver mode with a real AgentRunner
1114
- if (RECEIVER_MODE) {
1127
+ // Legacy turn watchdog only. Orchestration owns typing through durable task/decision state.
1128
+ if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) {
1115
1129
  typingManager.start(chat_id)
1116
1130
  }
1117
1131
  }
@@ -1157,12 +1171,13 @@ bot.command('help', async ctx => {
1157
1171
  `/rename <name> — rename current session\n` +
1158
1172
  `/clear — clear current session history\n` +
1159
1173
  `/compact — summarise and compress session history\n` +
1160
- `/stop — interrupt the running turn\n` +
1174
+ (ORCHESTRATION_ENABLED ? `/stop — stop the reply and choose a task to cancel\n` : `/stop — interrupt the running turn\n`) +
1161
1175
  `/restart — graceful restart session\n\n` +
1162
1176
  `*Agent*\n` +
1163
1177
  `/model — show current AI model\n` +
1164
1178
  `/models — switch AI model\n` +
1165
- `/cliopen the live terminal viewer\n\n` +
1179
+ (ORCHESTRATION_ENABLED ? `/tasksview pending tasks in this chat\n/voice — turn automatic voice replies on/off (default off)\n/voices — choose the agent voice\n` : '') +
1180
+ (INTERACTIVE_CLI_ENABLED ? `/cli — open the live terminal viewer\n\n` : '\n') +
1166
1181
  `*Account*\n` +
1167
1182
  `/start — pairing instructions\n` +
1168
1183
  `/status — check your pairing state`,
@@ -1295,6 +1310,7 @@ bot.command('models', async ctx => {
1295
1310
  // this bot's token), so nothing secret rides in the URL. Private chat + allowlist
1296
1311
  // only, matching every other command.
1297
1312
  bot.command('cli', async ctx => {
1313
+ if (!INTERACTIVE_CLI_ENABLED) { await ctx.reply('Terminal viewer requires interactive mode (headless: false).'); return }
1298
1314
  if (ctx.chat?.type !== 'private') return
1299
1315
  const access = loadAccess()
1300
1316
  if (!access.allowFrom.includes(String(ctx.from!.id))) return
@@ -1360,6 +1376,144 @@ bot.command('restart', async ctx => {
1360
1376
  )
1361
1377
  })
1362
1378
 
1379
+ type VoicePicker = {menuId:string;provider:string;page:number;pages:number;gender?:string;groups:string[];selected:string;voices:Array<{name:string;gender?:string;index:number;selected:boolean}>}
1380
+ async function voicePickerRequest(chatId:string,userId:string,payload:Record<string,unknown>={}) {
1381
+ if(!CALLBACK_URL_BASE)throw new Error('not_configured')
1382
+ const response=await fetch(CALLBACK_URL_BASE+'/command',{method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(20000),body:JSON.stringify({command:'telegram_voices',chat_id:chatId,payload:{...payload,user_id:userId}})})
1383
+ const result=await response.json() as VoicePicker & {success?:boolean;name?:string}
1384
+ if(!response.ok||!result.success)throw new Error('unavailable')
1385
+ return result
1386
+ }
1387
+ function voicePickerMenu(menu:VoicePicker) {
1388
+ const keyboard=new InlineKeyboard()
1389
+ const labels:Record<string,string>={male:'👨 Male',female:'👩 Female',neutral:'Neutral',unspecified:'Unspecified'}
1390
+ if(!menu.gender){
1391
+ for(const group of menu.groups)keyboard.text(labels[group],`voicegroup:${menu.menuId}:${group}`).row()
1392
+ }else{
1393
+ for(const v of menu.voices)keyboard.text(`${v.selected?'✅ ':''}${v.name.slice(0,60)}`,`voicepick:${menu.menuId}:${v.index}`).row()
1394
+ if(menu.page>0)keyboard.text('Previous',`voicepage:${menu.menuId}:${menu.gender}:${menu.page-1}`)
1395
+ if(menu.page+1<menu.pages)keyboard.text('Next',`voicepage:${menu.menuId}:${menu.gender}:${menu.page+1}`)
1396
+ keyboard.row().text('Back',`voicegroup:${menu.menuId}:back`).row()
1397
+ }
1398
+ keyboard.text('Dismiss',`voicedismiss:${menu.menuId}`)
1399
+ return {text:`Agent voice · ${menu.provider}\nSelected: ${menu.selected}\n${menu.gender?`${labels[menu.gender]} — choose a voice (${menu.page+1}/${menu.pages}):`:'Choose a voice category:'}`,reply_markup:keyboard}
1400
+ }
1401
+ bot.command('voices',async ctx=>{
1402
+ if(ctx.chat?.type!=='private'||!isCallbackAuthorized(ctx))return
1403
+ if(!ORCHESTRATION_ENABLED){await ctx.reply('Voice controls require orchestration mode.');return}
1404
+ try{const menu=voicePickerMenu(await voicePickerRequest(String(ctx.chat.id),String(ctx.from!.id)));await ctx.reply(menu.text,{reply_markup:menu.reply_markup})}
1405
+ catch{await ctx.reply('Voice list unavailable. Check the TTS provider configuration and try /voices again.')}
1406
+ })
1407
+
1408
+ type TaskView = TaskDetail & {taskId:string;title:string;state:string;progress?:string;question?:string;updatedAt:number;startedAt?:number;finishedAt?:number;canStop:boolean}
1409
+ type TaskBrowser = {sessionId:string;task?:TaskView;tasks?:TaskView[];page?:number;pages?:number;total?:number}
1410
+ async function taskBrowserRequest(chatId:string,userId:string,payload:Record<string,unknown>={}) {
1411
+ if(!CALLBACK_URL_BASE)throw Error('not_configured')
1412
+ const response=await fetch(CALLBACK_URL_BASE+'/command',{method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(10000),body:JSON.stringify({command:'telegram_tasks',chat_id:chatId,payload:{...payload,user_id:userId}})})
1413
+ const result=await response.json() as TaskBrowser & {success?:boolean}
1414
+ if(!response.ok||!result.success)throw Error((result as {error?:string}).error==='TASK_SESSION_CHANGED'?'TASK_SESSION_CHANGED':'unavailable')
1415
+ if(typeof result.sessionId!=='string'||!result.sessionId)throw Error('unavailable')
1416
+ return result
1417
+ }
1418
+ function taskBrowserMenu(result:TaskBrowser) {
1419
+ const keyboard=new InlineKeyboard()
1420
+ const labels:Record<string,string>={queued:'⏳ Queued',starting:'⏳ Starting',running:'🔥 Running',waiting_input:'💬 Waiting for your input',interrupting:'⏸ Interrupting',cancel_requested:'⏹ Stopping',recovering:'🔄 Recovering',needs_reconciliation:'⚠️ Needs reconciliation',completed:'✅ Completed',failed:'❌ Failed',cancelled:'⏹ Cancelled'}
1421
+ let text:string
1422
+ if(result.task){
1423
+ const task=result.task
1424
+ text=formatTaskDetail(task,labels[task.state]??task.state)
1425
+ if(task.canStop)keyboard.text(task.state==='needs_reconciliation'?'🔄 Retry cleanup':'🔴 Stop task',`taskcancel:${task.taskId}`).row()
1426
+ keyboard.text('Back','tasklist:0').row()
1427
+ }else{
1428
+ text=result.total?`Tasks (${result.total}) · Page ${(result.page??0)+1}/${result.pages}\nChoose a task for details.`:'No pending tasks in this chat.'
1429
+ for(const [i,task] of (result.tasks??[]).entries())keyboard.text(`${(result.page??0)*10+i+1}. ${task.title.replace(/\s+/g,' ').slice(0,45)} · ${labels[task.state]??task.state}`,`taskview:${task.taskId}`).row()
1430
+ if((result.page??0)>0)keyboard.text('Previous',`tasklist:${result.page!-1}`)
1431
+ if((result.page??0)+1<(result.pages??1))keyboard.text('Next',`tasklist:${result.page!+1}`)
1432
+ keyboard.row()
1433
+ }
1434
+ keyboard.text('Dismiss','taskdismiss')
1435
+ return {text,reply_markup:keyboard}
1436
+ }
1437
+ const TASK_BROWSER_FILE = join(STATE_DIR, 'task-browser.json')
1438
+ let restoredTaskBrowsers: TaskBrowserMessage[] = []
1439
+ try { const saved = JSON.parse(readFileSync(TASK_BROWSER_FILE, 'utf8')); if (Array.isArray(saved)) restoredTaskBrowsers = saved } catch {}
1440
+ const liveTaskBrowser = new LiveTaskBrowser({
1441
+ read: taskBrowserRequest,
1442
+ render: result => taskBrowserMenu(result as TaskBrowser),
1443
+ send: async (chat, menu) => (await bot.api.sendMessage(chat, menu.text, {reply_markup: menu.reply_markup as InlineKeyboard})).message_id,
1444
+ edit: (chat, id, menu) => bot.api.editMessageText(chat, id, menu.text, {reply_markup: menu.reply_markup as InlineKeyboard}),
1445
+ remove: (chat, id) => bot.api.deleteMessage(chat, id),
1446
+ close: (chat, id) => bot.api.editMessageText(chat, id, 'Task view closed.', {reply_markup: {inline_keyboard: []}}),
1447
+ allowed: (chat, user) => ORCHESTRATION_ENABLED && chat === user && loadAccess().allowFrom.includes(user),
1448
+ persist: entries => {
1449
+ mkdirSync(STATE_DIR, {recursive:true,mode:0o700})
1450
+ const temp = TASK_BROWSER_FILE + '.tmp'
1451
+ writeFileSync(temp,JSON.stringify(entries),{mode:0o600});renameSync(temp,TASK_BROWSER_FILE)
1452
+ },
1453
+ }, restoredTaskBrowsers)
1454
+ if (ORCHESTRATION_ENABLED) setInterval(() => { void liveTaskBrowser.tick().catch(() => {}) }, 3000).unref()
1455
+ bot.command('tasks',async ctx=>{
1456
+ if(ctx.chat?.type!=='private'||!isCallbackAuthorized(ctx))return
1457
+ if(!ORCHESTRATION_ENABLED){await ctx.reply('Task controls require orchestration mode.');return}
1458
+ try{await liveTaskBrowser.open(String(ctx.chat.id),String(ctx.from!.id))}
1459
+ catch{await ctx.reply('Could not load tasks. Please try /tasks again.')}
1460
+ })
1461
+
1462
+ async function taskStopRequest(chatId: string, userId: string, menuId?: string, index?: number) {
1463
+ if (!CALLBACK_URL_BASE) throw new Error('not_configured')
1464
+ const response=await fetch(CALLBACK_URL_BASE+'/command', {
1465
+ method:'POST',headers:{'Content-Type':'application/json'},signal:AbortSignal.timeout(10000),
1466
+ body:JSON.stringify({command:'task_stop',chat_id:chatId,payload:{user_id:userId,menu_id:menuId,index}}),
1467
+ })
1468
+ const result=await response.json() as {success?:boolean;legacy?:boolean;text?:string;menuId?:string;tasks?:Array<{title:string;state:string}>}
1469
+ if (!response.ok || !result.success) throw new Error('unavailable')
1470
+ return result
1471
+ }
1472
+ bot.command('stop',async (ctx,next)=>{
1473
+ if (!ORCHESTRATION_ENABLED || ctx.chat?.type!=='private') return next()
1474
+ if (!isCallbackAuthorized(ctx)) return
1475
+ try {
1476
+ const result=await taskStopRequest(String(ctx.chat.id),String(ctx.from!.id))
1477
+ if (result.legacy) return
1478
+ const keyboard=new InlineKeyboard()
1479
+ for (const [i,task] of (result.tasks??[]).entries()) keyboard.text(`${i+1}. ${task.title.slice(0,65)} (${task.state})`,`taskstop:${result.menuId}:${i+1}`).row()
1480
+ if (result.tasks?.length) keyboard.text('Dismiss',`taskstop:${result.menuId}:0`)
1481
+ await ctx.reply(result.text??'Nothing to stop.',result.tasks?.length?{reply_markup:keyboard}:{})
1482
+ } catch { await ctx.reply('Could not load tasks. Please try /stop again.') }
1483
+ })
1484
+
1485
+ // Voice settings are handled directly; no model call or TTS for this menu.
1486
+ type VoiceReplyMode = 'on' | 'auto' | 'off'
1487
+ async function telegramVoiceSetting(chatId: string, mode?: VoiceReplyMode): Promise<VoiceReplyMode> {
1488
+ if (!CALLBACK_URL_BASE) throw new Error('not_configured')
1489
+ const response = await fetch(CALLBACK_URL_BASE + '/command', {
1490
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
1491
+ body: JSON.stringify({ command: 'telegram_voice', chat_id: chatId, payload: { mode } }),
1492
+ signal: AbortSignal.timeout(10000),
1493
+ })
1494
+ const result = await response.json() as { success?: boolean; enabled?: boolean; mode?: VoiceReplyMode }
1495
+ if (!response.ok || !result.success || typeof result.enabled !== 'boolean') throw new Error('unavailable')
1496
+ return result.mode ?? (result.enabled ? 'on' : 'off')
1497
+ }
1498
+ function voiceMenu(mode: VoiceReplyMode) {
1499
+ const labels = {on:'🔊 Always', auto:'🎙️ Only reply voice message', off:'🔇 Off'}
1500
+ const keyboard = new InlineKeyboard()
1501
+ for (const value of ['on','auto','off'] as const) keyboard.text(`${mode === value ? '✅ ' : ''}${labels[value]}`, `voice:${value}`).row()
1502
+ return {text: `Voice replies: ${labels[mode]}\nAuto replies with audio to voice messages and their task results.`, reply_markup: keyboard.text('Dismiss','voice:dismiss')}
1503
+ }
1504
+ bot.command('voice', async ctx => {
1505
+ if (!ORCHESTRATION_ENABLED) { await ctx.reply('Voice controls require orchestration mode.'); return }
1506
+ if (ctx.chat?.type !== 'private' || !isCallbackAuthorized(ctx)) return
1507
+ const choice = ctx.match.trim().toLowerCase()
1508
+ if (choice && choice !== 'on' && choice !== 'off' && choice !== 'auto') {
1509
+ await ctx.reply('Use /voice to open the menu, or /voice on, /voice auto and /voice off.'); return
1510
+ }
1511
+ try {
1512
+ const menu = voiceMenu(await telegramVoiceSetting(String(ctx.chat.id), choice ? choice as VoiceReplyMode : undefined))
1513
+ await ctx.reply(menu.text, choice ? {} : { reply_markup: menu.reply_markup })
1514
+ } catch { await ctx.reply('Voice settings are unavailable. Check the agent TTS configuration.') }
1515
+ })
1516
+
1363
1517
  // /session — show current session info (direct command, no typing manager)
1364
1518
  bot.command('session', async ctx => {
1365
1519
  if (ctx.chat?.type !== 'private') return
@@ -1481,6 +1635,69 @@ function validModelRows(value: unknown): Array<{ id: string; label: string }> {
1481
1635
  bot.on('callback_query:data', async ctx => {
1482
1636
  const data = ctx.callbackQuery.data
1483
1637
 
1638
+ const voicePicker=/^(voicepick|voicepage|voicegroup|voicedismiss):([a-f0-9-]{36})(?::(male|female|neutral|unspecified|back))?(?::(\d+))?$/.exec(data)
1639
+ if(voicePicker){
1640
+ if(!ORCHESTRATION_ENABLED||ctx.callbackQuery.message?.chat.type!=='private'||!isCallbackAuthorized(ctx)){await ctx.answerCallbackQuery({text:'Not available or not authorized.'}).catch(()=>{});return}
1641
+ try{
1642
+ const action=voicePicker[1],id=voicePicker[2],n=Number(voicePicker[4]),chatId=String(ctx.callbackQuery.message.chat.id),userId=String(ctx.from.id)
1643
+ if(action==='voicepage'||action==='voicegroup'){
1644
+ const menu=voicePickerMenu(await voicePickerRequest(chatId,userId,{menu_id:id,page:action==='voicepage'?n:0,gender:voicePicker[3]==='back'?undefined:voicePicker[3]}))
1645
+ await ctx.editMessageText(menu.text,{reply_markup:menu.reply_markup}).catch(()=>{});await ctx.answerCallbackQuery().catch(()=>{})
1646
+ }else{
1647
+ const result=await voicePickerRequest(chatId,userId,{action:action==='voicepick'?'choose':'dismiss',menu_id:id,index:n})
1648
+ await ctx.answerCallbackQuery({text:action==='voicepick'?`Selected: ${result.name}`.slice(0,180):'Dismissed'}).catch(()=>{})
1649
+ if(action==='voicepick') await confirmSelection(ctx,`✅ Agent voice: ${result.name} · ${result.provider}`)
1650
+ else await ctx.deleteMessage().catch(()=>{})
1651
+ }
1652
+ }catch{await ctx.answerCallbackQuery({text:'Voice list changed or unavailable. Use /voices again.'}).catch(()=>{})}
1653
+ return
1654
+ }
1655
+
1656
+ const taskBrowser=/^(taskview|taskcancel):([a-f0-9-]{36})$|^(tasklist):(\d+)$|^(taskdismiss)$/.exec(data)
1657
+ if(taskBrowser){
1658
+ if(!ORCHESTRATION_ENABLED||ctx.callbackQuery.message?.chat.type!=='private'||!isCallbackAuthorized(ctx)){await ctx.answerCallbackQuery({text:'Not available or not authorized.'}).catch(()=>{});return}
1659
+ try{
1660
+ await ctx.answerCallbackQuery().catch(()=>{})
1661
+ const payload=data==='taskdismiss'?{action:'dismiss'}:taskBrowser[1]?{action:taskBrowser[1]==='taskcancel'?'cancel':'detail',task_id:taskBrowser[2]}:{page:Number(taskBrowser[4])}
1662
+ await liveTaskBrowser.navigate(String(ctx.callbackQuery.message.chat.id),String(ctx.from.id),ctx.callbackQuery.message.message_id,payload)
1663
+ }catch{await ctx.answerCallbackQuery({text:'Task unavailable. Use /tasks to refresh.'}).catch(()=>{})}
1664
+ return
1665
+ }
1666
+
1667
+ const taskStop=/^taskstop:([a-f0-9-]{36}):(\d+)$/.exec(data)
1668
+ if (taskStop) {
1669
+ if (!ORCHESTRATION_ENABLED) { await ctx.answerCallbackQuery({text:'Task controls require orchestration mode.'}).catch(()=>{}); return }
1670
+ if (ctx.callbackQuery.message?.chat.type!=='private' || !isCallbackAuthorized(ctx)) {
1671
+ await ctx.answerCallbackQuery({text:'Not authorized.'}).catch(()=>{}); return
1672
+ }
1673
+ try {
1674
+ const result=await taskStopRequest(String(ctx.callbackQuery.message.chat.id),String(ctx.from.id),taskStop[1],Number(taskStop[2]))
1675
+ await ctx.answerCallbackQuery({text:Number(taskStop[2])===0?'Dismissed':'Stop requested.'}).catch(()=>{})
1676
+ await ctx.deleteMessage().catch(()=>{})
1677
+ if (Number(taskStop[2])!==0) await ctx.reply(result.text??'Stop requested.')
1678
+ } catch { await ctx.answerCallbackQuery({text:'Selection unavailable. Use /stop to refresh.'}).catch(()=>{}) }
1679
+ return
1680
+ }
1681
+
1682
+ if (data === 'voice:on' || data === 'voice:auto' || data === 'voice:off' || data === 'voice:dismiss') {
1683
+ if (!ORCHESTRATION_ENABLED) { await ctx.answerCallbackQuery({text:'Voice controls require orchestration mode.'}).catch(()=>{}); return }
1684
+ if (ctx.callbackQuery.message?.chat.type !== 'private' || !isCallbackAuthorized(ctx)) {
1685
+ await ctx.answerCallbackQuery({ text: 'Not authorized.' }).catch(() => {}); return
1686
+ }
1687
+ if (data === 'voice:dismiss') {
1688
+ await ctx.answerCallbackQuery({ text: 'Dismissed' }).catch(() => {})
1689
+ await ctx.deleteMessage().catch(() => {})
1690
+ return
1691
+ }
1692
+ try {
1693
+ await telegramVoiceSetting(String(ctx.callbackQuery.message.chat.id), data.slice(6) as VoiceReplyMode)
1694
+ const confirmation=voiceModeConfirmation(data.slice(6))
1695
+ await ctx.answerCallbackQuery({text:confirmation}).catch(()=>{})
1696
+ await confirmSelection(ctx,confirmation)
1697
+ } catch { await ctx.answerCallbackQuery({ text: 'Could not update voice settings. Please try again.' }).catch(() => {}) }
1698
+ return
1699
+ }
1700
+
1484
1701
  // Handle interactive-menu choice: choice:<N>. A tap is routed back exactly like
1485
1702
  // the user typing "N" — same content + meta as the text path — so the session's
1486
1703
  // pending-menu handler injects the selection into the PTY. Security mirrors the
@@ -1514,7 +1731,7 @@ bot.on('callback_query:data', async ctx => {
1514
1731
  }).catch(err => {
1515
1732
  process.stderr.write(`telegram channel: choice callback POST failed: ${err}\n`)
1516
1733
  })
1517
- if (RECEIVER_MODE) typingManager.start(chat_id)
1734
+ if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) typingManager.start(chat_id)
1518
1735
  }
1519
1736
  return
1520
1737
  }
@@ -1732,7 +1949,7 @@ bot.on('callback_query:data', async ctx => {
1732
1949
  await ctx.answerCallbackQuery({ text: 'Compacting...' }).catch(() => {})
1733
1950
  await ctx.deleteMessage().catch(() => {})
1734
1951
  await ctx.reply('🧠 Session compacting, please wait...\nThis may take a moment.').catch(() => {})
1735
- if (RECEIVER_MODE) {
1952
+ if (RECEIVER_MODE && !ORCHESTRATION_ENABLED) {
1736
1953
  typingManager.start(chatId)
1737
1954
  }
1738
1955
  await fetch(CALLBACK_URL_BASE + '/command', {
@@ -1978,6 +2195,10 @@ bot.on('message:text', async ctx => {
1978
2195
 
1979
2196
  bot.on('message:photo', async ctx => {
1980
2197
  const caption = ctx.message.caption ?? '(photo)'
2198
+ if(orchestrationSpool){
2199
+ const photo=ctx.message.photo[ctx.message.photo.length-1]!;
2200
+ await handleInbound(ctx,caption,undefined,{kind:'photo',file_id:photo.file_id,size:photo.file_size,mime:'image/jpeg'});return;
2201
+ }
1981
2202
  // Defer download until after the gate approves — any user can send photos,
1982
2203
  // and we don't want to burn API quota or fill the inbox for dropped messages.
1983
2204
  await handleInbound(ctx, caption, async () => {
@@ -0,0 +1,20 @@
1
+ /** Keep a durable confirmation in chat, matching the /models menu behavior. */
2
+ export async function confirmSelection(ctx: {
3
+ editMessageText(text: string, options: {reply_markup: {inline_keyboard: never[]}}): Promise<unknown>;
4
+ reply(text: string): Promise<unknown>;
5
+ deleteMessage(): Promise<unknown>;
6
+ }, text: string): Promise<void> {
7
+ try { await ctx.editMessageText(text, {reply_markup: {inline_keyboard: []}}); }
8
+ catch (error) {
9
+ if (/message is not modified/i.test(String((error as any)?.description ?? (error as any)?.message))) return;
10
+ // If Telegram cannot edit an old menu, retain the confirmation as a new
11
+ // message before removing the old controls. Never lose both.
12
+ await ctx.reply(text);
13
+ await ctx.deleteMessage().catch(() => {});
14
+ }
15
+ }
16
+ export function voiceModeConfirmation(mode: string): string {
17
+ const labels: Record<string,string> = {on:'Always',auto:'Only reply voice message',off:'Off'};
18
+ if (!(mode in labels)) throw Error('INVALID_VOICE_MODE');
19
+ return `✅ Voice replies: ${labels[mode]}`;
20
+ }
@@ -0,0 +1,117 @@
1
+ /** A single live task browser per private chat. All reads are session-bound and
2
+ * serialized with navigation, replacement, and explicit cancellation. */
3
+ export interface TaskBrowserMessage {
4
+ chatId: string; userId: string; messageId: number; sessionId: string;
5
+ view: { page?: number; action?: 'detail'; task_id?: string };
6
+ }
7
+ export interface TaskBrowserResult {
8
+ sessionId: string;
9
+ task?: { taskId: string; state: string };
10
+ page?: number;
11
+ }
12
+ export interface TaskBrowserMenu { text: string; reply_markup: unknown }
13
+ interface Entry extends TaskBrowserMessage { fingerprint?: string; retryAt?: number; finished?: boolean }
14
+ interface Dependencies {
15
+ read(chat: string, user: string, payload: Record<string, unknown>): Promise<TaskBrowserResult>;
16
+ render(result: TaskBrowserResult): TaskBrowserMenu;
17
+ send(chat: string, menu: TaskBrowserMenu): Promise<number>;
18
+ edit(chat: string, id: number, menu: TaskBrowserMenu): Promise<unknown>;
19
+ remove(chat: string, id: number): Promise<unknown>;
20
+ close(chat: string, id: number): Promise<unknown>;
21
+ allowed(chat: string, user: string): boolean;
22
+ persist(entries: TaskBrowserMessage[]): void;
23
+ now?: () => number;
24
+ }
25
+ const description = (e: any) => String(e?.description ?? e?.message ?? '');
26
+ const absent = (e: any) => /message to (?:edit|delete) not found|message_id_invalid/i.test(description(e));
27
+ const unchanged = (e: any) => /message is not modified/i.test(description(e));
28
+ export class LiveTaskBrowser {
29
+ private entries = new Map<string, Entry>();
30
+ private locks = new Map<string, Promise<unknown>>();
31
+ private nextEditAt = 0;
32
+ constructor(private io: Dependencies, restored: TaskBrowserMessage[] = []) {
33
+ for (const e of restored) {
34
+ if (e && typeof e === 'object' && /^\d+$/.test(e.chatId) && e.userId === e.chatId && Number.isSafeInteger(e.messageId) && e.messageId > 0 && typeof e.sessionId === 'string' && e.view &&
35
+ (e.view.action === 'detail' ? typeof e.view.task_id === 'string' : Number.isSafeInteger(e.view.page ?? 0))) this.entries.set(e.chatId, { ...e });
36
+ }
37
+ }
38
+ private now() { return this.io.now?.() ?? Date.now(); }
39
+ private persist() {
40
+ this.io.persist([...this.entries.values()].map(({chatId,userId,messageId,sessionId,view}) => ({chatId,userId,messageId,sessionId,view})));
41
+ }
42
+ private serial<T>(chat: string, work: () => Promise<T>): Promise<T> {
43
+ const previous = this.locks.get(chat) ?? Promise.resolve();
44
+ const job = previous.catch(() => {}).then(work);
45
+ this.locks.set(chat, job);
46
+ void job.finally(() => { if (this.locks.get(chat) === job) this.locks.delete(chat); }).catch(() => {});
47
+ return job;
48
+ }
49
+ private async retire(e: Entry) {
50
+ try { await this.io.remove(e.chatId, e.messageId); }
51
+ catch (error) {
52
+ if (!absent(error)) {
53
+ try { await this.io.close(e.chatId, e.messageId); }
54
+ catch (closed) { if (!absent(closed) && !unchanged(closed)) throw closed; }
55
+ }
56
+ }
57
+ this.entries.delete(e.chatId); this.persist();
58
+ }
59
+ open(chatId: string, userId: string) {
60
+ return this.serial(chatId, async () => {
61
+ if (!this.io.allowed(chatId, userId)) throw Error('TASK_BROWSER_UNAUTHORIZED');
62
+ const result = await this.io.read(chatId, userId, { page: 0 });
63
+ const menu = this.io.render(result);
64
+ const previous = this.entries.get(chatId);
65
+ if (previous) await this.retire(previous);
66
+ const messageId = await this.io.send(chatId, menu);
67
+ this.entries.set(chatId, {chatId,userId,messageId,sessionId:result.sessionId,view:{page:result.page ?? 0},fingerprint:JSON.stringify(menu)});
68
+ this.persist();
69
+ });
70
+ }
71
+ navigate(chatId: string, userId: string, messageId: number, payload: Record<string, unknown>) {
72
+ return this.serial(chatId, async () => {
73
+ const e = this.entries.get(chatId);
74
+ if (!e || e.messageId !== messageId || e.userId !== userId || !this.io.allowed(chatId,userId)) throw Error('TASK_BROWSER_EXPIRED');
75
+ if (payload.action === 'dismiss') { await this.retire(e); return; }
76
+ try {
77
+ const result = await this.io.read(chatId,userId,{...payload,session_id:e.sessionId});
78
+ // Never repeat a mutation during auto-refresh, even if Telegram edit fails.
79
+ e.view = result.task ? {action:'detail',task_id:result.task.taskId} : {page:result.page ?? 0};
80
+ this.persist();
81
+ await this.update(e,result);
82
+ } catch (error) { await this.failure(e,error); throw error; }
83
+ });
84
+ }
85
+ private async update(e: Entry, result: TaskBrowserResult) {
86
+ if (result.sessionId !== e.sessionId) throw Error('TASK_SESSION_CHANGED');
87
+ const view = result.task ? { action: 'detail' as const, task_id: result.task.taskId } : { page: result.page ?? 0 };
88
+ if (JSON.stringify(view) !== JSON.stringify(e.view)) { e.view = view; this.persist(); }
89
+ const finished = !!result.task && ['completed','failed','cancelled'].includes(result.task.state);
90
+ const menu = this.io.render(result), fingerprint = JSON.stringify(menu);
91
+ if (fingerprint === e.fingerprint) { e.finished = finished; return; }
92
+ try { await this.io.edit(e.chatId,e.messageId,menu); }
93
+ catch (error) { if (!unchanged(error)) throw error; }
94
+ e.fingerprint = fingerprint; e.retryAt = undefined; e.finished = finished;
95
+ }
96
+ private async failure(e: Entry, error: any) {
97
+ if (description(error).includes('TASK_SESSION_CHANGED')) { await this.retire(e); return; }
98
+ if (absent(error) || error?.error_code === 403) { this.entries.delete(e.chatId); this.persist(); return; }
99
+ const retry = Number(error?.parameters?.retry_after);
100
+ e.retryAt = this.now() + (Number.isFinite(retry) && retry > 0 ? retry * 1000 : 10000);
101
+ if (retry > 0) this.nextEditAt = Math.max(this.nextEditAt,e.retryAt);
102
+ }
103
+ async tick() {
104
+ // One tick cannot overlap a previous read/edit or user action for this chat.
105
+ for (const e of [...this.entries.values()]) {
106
+ if (e.finished || this.locks.has(e.chatId) || this.now() < Math.max(e.retryAt ?? 0,this.nextEditAt)) continue;
107
+ await this.serial(e.chatId, async () => {
108
+ if (this.entries.get(e.chatId) !== e) return;
109
+ if (!this.io.allowed(e.chatId,e.userId)) {
110
+ this.entries.delete(e.chatId); this.persist(); return;
111
+ }
112
+ try { await this.update(e,await this.io.read(e.chatId,e.userId,{...e.view,session_id:e.sessionId})); }
113
+ catch (error) { await this.failure(e,error).catch(() => {}); }
114
+ });
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,36 @@
1
+ import { formatTiming } from '../../../dist/orchestration/tasks/timing';
2
+ import { formatTaskElapsed } from './task-elapsed';
3
+ import type { ProcessSample } from '../../../src/orchestration/process-activity';
4
+ import type { ToolOutcome } from '../../../src/orchestration/execution-observation';
5
+
6
+ export interface TaskDetail {
7
+ title: string; state: string; startedAt?: number; finishedAt?: number;
8
+ timing?: Partial<Record<'working' | 'tool' | 'input' | 'queued' | 'other' | 'finished', number>>;
9
+ timingMeasuredFrom?: number;
10
+ progress?: string; progressText?: string; question?: string;
11
+ activityDetails?: { counters: string; status: string; age: number; activeTools: string[];
12
+ observationStatus?: string; process?: ProcessSample; lastTool?: ToolOutcome; quiet?: boolean };
13
+ }
14
+ function bytes(value: number): string {
15
+ const unit = value >= 1000000 ? 'MB' : value >= 1000 ? 'KB' : 'B';
16
+ return `${Number((value / (unit === 'MB' ? 1000000 : unit === 'KB' ? 1000 : 1)).toFixed(2))} ${unit}`;
17
+ }
18
+ export function formatTaskDetail(task: TaskDetail, label: string): string {
19
+ const activity = task.activityDetails;
20
+ const progress = task.progressText ?? task.progress;
21
+ const progressLines = [progress, activity?.activeTools.length ? `Active tools: ${activity.activeTools.join(', ')}` : '', activity ? `Last activity: ${activity.age}s ago.` : '', task.question ? `Waiting for your input:\n${task.question}` : ''].filter(Boolean);
22
+ const process = activity?.process;
23
+ const counters = process ? process.available ? [
24
+ `Processes: ${process.processCount}`,
25
+ process.cpuTicksDelta !== undefined ? `CPU: +${process.cpuTicksDelta} ticks · Children: ${process.childCpuTicksDelta === undefined ? 'unavailable' : `+${process.childCpuTicksDelta} ticks`}` : 'CPU: unavailable',
26
+ process.ioAvailable ? `I/O: Read ${process.readBytesDelta === undefined ? 'unavailable' : `+${bytes(process.readBytesDelta)}`} · Write ${process.writeBytesDelta === undefined ? 'unavailable' : `+${bytes(process.writeBytesDelta)}`}` : 'I/O: partially unavailable',
27
+ ].join('\n') : '' : activity?.counters;
28
+ const lastTool = activity?.lastTool;
29
+ const diagnostics = [counters, lastTool ? `Last tool: ${lastTool.name} · ${lastTool.status === 'error' ? 'Reported error' : 'Result received'}${lastTool.exitCode === undefined ? '' : ` · Exit code: ${lastTool.exitCode}`}\nReported at: ${new Date(lastTool.observedAt).toISOString().replace('T',' ').replace(/\.\d{3}Z$/, ' UTC')}` : '', activity?.observationStatus ?? activity?.status, activity?.quiet ? 'No recent tool/model progress; task retained for inspection or cancellation.' : ''].filter(Boolean);
30
+ return [
31
+ `${task.title}\n${label} · Elapsed: ${formatTaskElapsed(task.startedAt,task.finishedAt)}`,
32
+ formatTiming(task.timing) ? 'Time breakdown\n' + formatTiming(task.timing) + (task.timingMeasuredFrom && task.startedAt && task.timingMeasuredFrom > task.startedAt + 1000 ? '\nMeasured since ' + new Date(task.timingMeasuredFrom).toISOString() : '') : '',
33
+ progressLines.length ? `Progress\n${progressLines.join('\n')}` : '',
34
+ diagnostics.length ? `Diagnostics\n${diagnostics.join('\n')}` : '',
35
+ ].filter(Boolean).join('\n\n');
36
+ }
@@ -0,0 +1 @@
1
+ export { formatTaskElapsed } from '../../../dist/shared/task-elapsed';