@0xmaxma/claude-gateway 1.8.14 โ†’ 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 (582) hide show
  1. package/README.md +28 -1613
  2. package/config.template.json +7 -1
  3. package/dist/agent/runner.d.ts +377 -0
  4. package/dist/agent/runner.d.ts.map +1 -1
  5. package/dist/agent/runner.js +680 -24
  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 +135 -10
  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/apps/agent-container-migration.d.ts +11 -0
  52. package/dist/apps/agent-container-migration.d.ts.map +1 -0
  53. package/dist/apps/agent-container-migration.js +85 -0
  54. package/dist/apps/agent-container-migration.js.map +1 -0
  55. package/dist/apps/installer.d.ts +1 -1
  56. package/dist/apps/installer.d.ts.map +1 -1
  57. package/dist/apps/installer.js +2 -1
  58. package/dist/apps/installer.js.map +1 -1
  59. package/dist/cli/commands/tasks.d.ts +5 -0
  60. package/dist/cli/commands/tasks.d.ts.map +1 -0
  61. package/dist/cli/commands/tasks.js +92 -0
  62. package/dist/cli/commands/tasks.js.map +1 -0
  63. package/dist/cli/http-client.d.ts +2 -0
  64. package/dist/cli/http-client.d.ts.map +1 -1
  65. package/dist/cli/http-client.js +8 -3
  66. package/dist/cli/http-client.js.map +1 -1
  67. package/dist/cli/index.d.ts.map +1 -1
  68. package/dist/cli/index.js +5 -1
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/config/config-write-lock.d.ts +2 -0
  71. package/dist/config/config-write-lock.d.ts.map +1 -1
  72. package/dist/config/config-write-lock.js +5 -0
  73. package/dist/config/config-write-lock.js.map +1 -1
  74. package/dist/config/loader.d.ts.map +1 -1
  75. package/dist/config/loader.js +87 -2
  76. package/dist/config/loader.js.map +1 -1
  77. package/dist/config/migrator.d.ts.map +1 -1
  78. package/dist/config/migrator.js +12 -1
  79. package/dist/config/migrator.js.map +1 -1
  80. package/dist/config/watcher.d.ts.map +1 -1
  81. package/dist/config/watcher.js +6 -0
  82. package/dist/config/watcher.js.map +1 -1
  83. package/dist/connectors/resolve.d.ts.map +1 -1
  84. package/dist/connectors/resolve.js +3 -1
  85. package/dist/connectors/resolve.js.map +1 -1
  86. package/dist/connectors/types.d.ts +8 -0
  87. package/dist/connectors/types.d.ts.map +1 -1
  88. package/dist/cron/manager.d.ts.map +1 -1
  89. package/dist/cron/manager.js +21 -2
  90. package/dist/cron/manager.js.map +1 -1
  91. package/dist/discord/receiver.d.ts +2 -1
  92. package/dist/discord/receiver.d.ts.map +1 -1
  93. package/dist/discord/receiver.js +5 -1
  94. package/dist/discord/receiver.js.map +1 -1
  95. package/dist/history/db.d.ts +3 -0
  96. package/dist/history/db.d.ts.map +1 -1
  97. package/dist/history/db.js +21 -1
  98. package/dist/history/db.js.map +1 -1
  99. package/dist/history/types.d.ts +2 -0
  100. package/dist/history/types.d.ts.map +1 -1
  101. package/dist/index.js +48 -9
  102. package/dist/index.js.map +1 -1
  103. package/dist/orchestration/bounded-queue.d.ts +15 -0
  104. package/dist/orchestration/bounded-queue.d.ts.map +1 -0
  105. package/dist/orchestration/bounded-queue.js +58 -0
  106. package/dist/orchestration/bounded-queue.js.map +1 -0
  107. package/dist/orchestration/bridge.d.ts +55 -0
  108. package/dist/orchestration/bridge.d.ts.map +1 -0
  109. package/dist/orchestration/bridge.js +234 -0
  110. package/dist/orchestration/bridge.js.map +1 -0
  111. package/dist/orchestration/browser-routing.d.ts +7 -0
  112. package/dist/orchestration/browser-routing.d.ts.map +1 -0
  113. package/dist/orchestration/browser-routing.js +22 -0
  114. package/dist/orchestration/browser-routing.js.map +1 -0
  115. package/dist/orchestration/capabilities.d.ts +49 -0
  116. package/dist/orchestration/capabilities.d.ts.map +1 -0
  117. package/dist/orchestration/capabilities.js +417 -0
  118. package/dist/orchestration/capabilities.js.map +1 -0
  119. package/dist/orchestration/capacity.d.ts +14 -0
  120. package/dist/orchestration/capacity.d.ts.map +1 -0
  121. package/dist/orchestration/capacity.js +52 -0
  122. package/dist/orchestration/capacity.js.map +1 -0
  123. package/dist/orchestration/channel-activity.d.ts +21 -0
  124. package/dist/orchestration/channel-activity.d.ts.map +1 -0
  125. package/dist/orchestration/channel-activity.js +72 -0
  126. package/dist/orchestration/channel-activity.js.map +1 -0
  127. package/dist/orchestration/channel-controls.d.ts +32 -0
  128. package/dist/orchestration/channel-controls.d.ts.map +1 -0
  129. package/dist/orchestration/channel-controls.js +105 -0
  130. package/dist/orchestration/channel-controls.js.map +1 -0
  131. package/dist/orchestration/channel-input-media.d.ts +11 -0
  132. package/dist/orchestration/channel-input-media.d.ts.map +1 -0
  133. package/dist/orchestration/channel-input-media.js +54 -0
  134. package/dist/orchestration/channel-input-media.js.map +1 -0
  135. package/dist/orchestration/channel-media.d.ts +4 -0
  136. package/dist/orchestration/channel-media.d.ts.map +1 -0
  137. package/dist/orchestration/channel-media.js +70 -0
  138. package/dist/orchestration/channel-media.js.map +1 -0
  139. package/dist/orchestration/channel-speech.d.ts +7 -0
  140. package/dist/orchestration/channel-speech.d.ts.map +1 -0
  141. package/dist/orchestration/channel-speech.js +65 -0
  142. package/dist/orchestration/channel-speech.js.map +1 -0
  143. package/dist/orchestration/cli-skills.d.ts +12 -0
  144. package/dist/orchestration/cli-skills.d.ts.map +1 -0
  145. package/dist/orchestration/cli-skills.js +132 -0
  146. package/dist/orchestration/cli-skills.js.map +1 -0
  147. package/dist/orchestration/config.d.ts +256 -0
  148. package/dist/orchestration/config.d.ts.map +1 -0
  149. package/dist/orchestration/config.js +110 -0
  150. package/dist/orchestration/config.js.map +1 -0
  151. package/dist/orchestration/container.d.ts +21 -0
  152. package/dist/orchestration/container.d.ts.map +1 -0
  153. package/dist/orchestration/container.js +195 -0
  154. package/dist/orchestration/container.js.map +1 -0
  155. package/dist/orchestration/control-delivery.d.ts +5 -0
  156. package/dist/orchestration/control-delivery.d.ts.map +1 -0
  157. package/dist/orchestration/control-delivery.js +50 -0
  158. package/dist/orchestration/control-delivery.js.map +1 -0
  159. package/dist/orchestration/conversation-intake.d.ts +32 -0
  160. package/dist/orchestration/conversation-intake.d.ts.map +1 -0
  161. package/dist/orchestration/conversation-intake.js +92 -0
  162. package/dist/orchestration/conversation-intake.js.map +1 -0
  163. package/dist/orchestration/decisions.d.ts +27 -0
  164. package/dist/orchestration/decisions.d.ts.map +1 -0
  165. package/dist/orchestration/decisions.js +148 -0
  166. package/dist/orchestration/decisions.js.map +1 -0
  167. package/dist/orchestration/delivery.d.ts +30 -0
  168. package/dist/orchestration/delivery.d.ts.map +1 -0
  169. package/dist/orchestration/delivery.js +219 -0
  170. package/dist/orchestration/delivery.js.map +1 -0
  171. package/dist/orchestration/display-stream.d.ts +4 -0
  172. package/dist/orchestration/display-stream.d.ts.map +1 -0
  173. package/dist/orchestration/display-stream.js +77 -0
  174. package/dist/orchestration/display-stream.js.map +1 -0
  175. package/dist/orchestration/events.d.ts +30 -0
  176. package/dist/orchestration/events.d.ts.map +1 -0
  177. package/dist/orchestration/events.js +65 -0
  178. package/dist/orchestration/events.js.map +1 -0
  179. package/dist/orchestration/execution-observation.d.ts +38 -0
  180. package/dist/orchestration/execution-observation.d.ts.map +1 -0
  181. package/dist/orchestration/execution-observation.js +25 -0
  182. package/dist/orchestration/execution-observation.js.map +1 -0
  183. package/dist/orchestration/file-delivery.d.ts +16 -0
  184. package/dist/orchestration/file-delivery.d.ts.map +1 -0
  185. package/dist/orchestration/file-delivery.js +139 -0
  186. package/dist/orchestration/file-delivery.js.map +1 -0
  187. package/dist/orchestration/gateway-config.d.ts +17 -0
  188. package/dist/orchestration/gateway-config.d.ts.map +1 -0
  189. package/dist/orchestration/gateway-config.js +128 -0
  190. package/dist/orchestration/gateway-config.js.map +1 -0
  191. package/dist/orchestration/history.d.ts +13 -0
  192. package/dist/orchestration/history.d.ts.map +1 -0
  193. package/dist/orchestration/history.js +48 -0
  194. package/dist/orchestration/history.js.map +1 -0
  195. package/dist/orchestration/identity.d.ts +6 -0
  196. package/dist/orchestration/identity.d.ts.map +1 -0
  197. package/dist/orchestration/identity.js +13 -0
  198. package/dist/orchestration/identity.js.map +1 -0
  199. package/dist/orchestration/inference-errors.d.ts +3 -0
  200. package/dist/orchestration/inference-errors.d.ts.map +1 -0
  201. package/dist/orchestration/inference-errors.js +16 -0
  202. package/dist/orchestration/inference-errors.js.map +1 -0
  203. package/dist/orchestration/input-images.d.ts +12 -0
  204. package/dist/orchestration/input-images.d.ts.map +1 -0
  205. package/dist/orchestration/input-images.js +66 -0
  206. package/dist/orchestration/input-images.js.map +1 -0
  207. package/dist/orchestration/instance-lock.d.ts +5 -0
  208. package/dist/orchestration/instance-lock.d.ts.map +1 -0
  209. package/dist/orchestration/instance-lock.js +33 -0
  210. package/dist/orchestration/instance-lock.js.map +1 -0
  211. package/dist/orchestration/line-loading.d.ts +21 -0
  212. package/dist/orchestration/line-loading.d.ts.map +1 -0
  213. package/dist/orchestration/line-loading.js +89 -0
  214. package/dist/orchestration/line-loading.js.map +1 -0
  215. package/dist/orchestration/media.d.ts +3 -0
  216. package/dist/orchestration/media.d.ts.map +1 -0
  217. package/dist/orchestration/media.js +46 -0
  218. package/dist/orchestration/media.js.map +1 -0
  219. package/dist/orchestration/migrations/schema.d.ts +3 -0
  220. package/dist/orchestration/migrations/schema.d.ts.map +1 -0
  221. package/dist/orchestration/migrations/schema.js +80 -0
  222. package/dist/orchestration/migrations/schema.js.map +1 -0
  223. package/dist/orchestration/notification-mailbox.d.ts +5 -0
  224. package/dist/orchestration/notification-mailbox.d.ts.map +1 -0
  225. package/dist/orchestration/notification-mailbox.js +29 -0
  226. package/dist/orchestration/notification-mailbox.js.map +1 -0
  227. package/dist/orchestration/process-activity.d.ts +23 -0
  228. package/dist/orchestration/process-activity.d.ts.map +1 -0
  229. package/dist/orchestration/process-activity.js +95 -0
  230. package/dist/orchestration/process-activity.js.map +1 -0
  231. package/dist/orchestration/process-supervisor.d.ts +15 -0
  232. package/dist/orchestration/process-supervisor.d.ts.map +1 -0
  233. package/dist/orchestration/process-supervisor.js +92 -0
  234. package/dist/orchestration/process-supervisor.js.map +1 -0
  235. package/dist/orchestration/process-turn.d.ts +35 -0
  236. package/dist/orchestration/process-turn.d.ts.map +1 -0
  237. package/dist/orchestration/process-turn.js +246 -0
  238. package/dist/orchestration/process-turn.js.map +1 -0
  239. package/dist/orchestration/progress-review.d.ts +28 -0
  240. package/dist/orchestration/progress-review.d.ts.map +1 -0
  241. package/dist/orchestration/progress-review.js +48 -0
  242. package/dist/orchestration/progress-review.js.map +1 -0
  243. package/dist/orchestration/recovery.d.ts +8 -0
  244. package/dist/orchestration/recovery.d.ts.map +1 -0
  245. package/dist/orchestration/recovery.js +35 -0
  246. package/dist/orchestration/recovery.js.map +1 -0
  247. package/dist/orchestration/reply-context.d.ts +7 -0
  248. package/dist/orchestration/reply-context.d.ts.map +1 -0
  249. package/dist/orchestration/reply-context.js +54 -0
  250. package/dist/orchestration/reply-context.js.map +1 -0
  251. package/dist/orchestration/runtime.d.ts +232 -0
  252. package/dist/orchestration/runtime.d.ts.map +1 -0
  253. package/dist/orchestration/runtime.js +962 -0
  254. package/dist/orchestration/runtime.js.map +1 -0
  255. package/dist/orchestration/skills.d.ts +17 -0
  256. package/dist/orchestration/skills.d.ts.map +1 -0
  257. package/dist/orchestration/skills.js +32 -0
  258. package/dist/orchestration/skills.js.map +1 -0
  259. package/dist/orchestration/source-policy.d.ts +6 -0
  260. package/dist/orchestration/source-policy.d.ts.map +1 -0
  261. package/dist/orchestration/source-policy.js +16 -0
  262. package/dist/orchestration/source-policy.js.map +1 -0
  263. package/dist/orchestration/speech.d.ts +24 -0
  264. package/dist/orchestration/speech.d.ts.map +1 -0
  265. package/dist/orchestration/speech.js +61 -0
  266. package/dist/orchestration/speech.js.map +1 -0
  267. package/dist/orchestration/stop-controls.d.ts +30 -0
  268. package/dist/orchestration/stop-controls.d.ts.map +1 -0
  269. package/dist/orchestration/stop-controls.js +79 -0
  270. package/dist/orchestration/stop-controls.js.map +1 -0
  271. package/dist/orchestration/store.d.ts +86 -0
  272. package/dist/orchestration/store.d.ts.map +1 -0
  273. package/dist/orchestration/store.js +275 -0
  274. package/dist/orchestration/store.js.map +1 -0
  275. package/dist/orchestration/task-controls.d.ts +130 -0
  276. package/dist/orchestration/task-controls.d.ts.map +1 -0
  277. package/dist/orchestration/task-controls.js +55 -0
  278. package/dist/orchestration/task-controls.js.map +1 -0
  279. package/dist/orchestration/task-files.d.ts +29 -0
  280. package/dist/orchestration/task-files.d.ts.map +1 -0
  281. package/dist/orchestration/task-files.js +200 -0
  282. package/dist/orchestration/task-files.js.map +1 -0
  283. package/dist/orchestration/task-report.d.ts +7 -0
  284. package/dist/orchestration/task-report.d.ts.map +1 -0
  285. package/dist/orchestration/task-report.js +33 -0
  286. package/dist/orchestration/task-report.js.map +1 -0
  287. package/dist/orchestration/tasks/checkpoint-hook.d.ts +19 -0
  288. package/dist/orchestration/tasks/checkpoint-hook.d.ts.map +1 -0
  289. package/dist/orchestration/tasks/checkpoint-hook.js +49 -0
  290. package/dist/orchestration/tasks/checkpoint-hook.js.map +1 -0
  291. package/dist/orchestration/tasks/cleanup.d.ts +18 -0
  292. package/dist/orchestration/tasks/cleanup.d.ts.map +1 -0
  293. package/dist/orchestration/tasks/cleanup.js +113 -0
  294. package/dist/orchestration/tasks/cleanup.js.map +1 -0
  295. package/dist/orchestration/tasks/driver.d.ts +23 -0
  296. package/dist/orchestration/tasks/driver.d.ts.map +1 -0
  297. package/dist/orchestration/tasks/driver.js +209 -0
  298. package/dist/orchestration/tasks/driver.js.map +1 -0
  299. package/dist/orchestration/tasks/failure.d.ts +5 -0
  300. package/dist/orchestration/tasks/failure.d.ts.map +1 -0
  301. package/dist/orchestration/tasks/failure.js +17 -0
  302. package/dist/orchestration/tasks/failure.js.map +1 -0
  303. package/dist/orchestration/tasks/pool.d.ts +18 -0
  304. package/dist/orchestration/tasks/pool.d.ts.map +1 -0
  305. package/dist/orchestration/tasks/pool.js +73 -0
  306. package/dist/orchestration/tasks/pool.js.map +1 -0
  307. package/dist/orchestration/tasks/scheduler.d.ts +38 -0
  308. package/dist/orchestration/tasks/scheduler.d.ts.map +1 -0
  309. package/dist/orchestration/tasks/scheduler.js +160 -0
  310. package/dist/orchestration/tasks/scheduler.js.map +1 -0
  311. package/dist/orchestration/tasks/service.d.ts +109 -0
  312. package/dist/orchestration/tasks/service.d.ts.map +1 -0
  313. package/dist/orchestration/tasks/service.js +697 -0
  314. package/dist/orchestration/tasks/service.js.map +1 -0
  315. package/dist/orchestration/tasks/shared-workspace.d.ts +25 -0
  316. package/dist/orchestration/tasks/shared-workspace.d.ts.map +1 -0
  317. package/dist/orchestration/tasks/shared-workspace.js +162 -0
  318. package/dist/orchestration/tasks/shared-workspace.js.map +1 -0
  319. package/dist/orchestration/tasks/task-directive.d.ts +6 -0
  320. package/dist/orchestration/tasks/task-directive.d.ts.map +1 -0
  321. package/dist/orchestration/tasks/task-directive.js +41 -0
  322. package/dist/orchestration/tasks/task-directive.js.map +1 -0
  323. package/dist/orchestration/tasks/timing.d.ts +15 -0
  324. package/dist/orchestration/tasks/timing.d.ts.map +1 -0
  325. package/dist/orchestration/tasks/timing.js +48 -0
  326. package/dist/orchestration/tasks/timing.js.map +1 -0
  327. package/dist/orchestration/tasks/tool-repetition.d.ts +3 -0
  328. package/dist/orchestration/tasks/tool-repetition.d.ts.map +1 -0
  329. package/dist/orchestration/tasks/tool-repetition.js +30 -0
  330. package/dist/orchestration/tasks/tool-repetition.js.map +1 -0
  331. package/dist/orchestration/tasks/workspace.d.ts +24 -0
  332. package/dist/orchestration/tasks/workspace.d.ts.map +1 -0
  333. package/dist/orchestration/tasks/workspace.js +147 -0
  334. package/dist/orchestration/tasks/workspace.js.map +1 -0
  335. package/dist/orchestration/telegram-speech.d.ts +17 -0
  336. package/dist/orchestration/telegram-speech.d.ts.map +1 -0
  337. package/dist/orchestration/telegram-speech.js +55 -0
  338. package/dist/orchestration/telegram-speech.js.map +1 -0
  339. package/dist/orchestration/telegram-tool-status.d.ts +23 -0
  340. package/dist/orchestration/telegram-tool-status.d.ts.map +1 -0
  341. package/dist/orchestration/telegram-tool-status.js +207 -0
  342. package/dist/orchestration/telegram-tool-status.js.map +1 -0
  343. package/dist/orchestration/telegram-voices.d.ts +42 -0
  344. package/dist/orchestration/telegram-voices.d.ts.map +1 -0
  345. package/dist/orchestration/telegram-voices.js +70 -0
  346. package/dist/orchestration/telegram-voices.js.map +1 -0
  347. package/dist/orchestration/tool-activity.d.ts +10 -0
  348. package/dist/orchestration/tool-activity.d.ts.map +1 -0
  349. package/dist/orchestration/tool-activity.js +38 -0
  350. package/dist/orchestration/tool-activity.js.map +1 -0
  351. package/dist/orchestration/types.d.ts +177 -0
  352. package/dist/orchestration/types.d.ts.map +1 -0
  353. package/dist/orchestration/types.js +13 -0
  354. package/dist/orchestration/types.js.map +1 -0
  355. package/dist/orchestration/voice-reply-policy.d.ts +8 -0
  356. package/dist/orchestration/voice-reply-policy.d.ts.map +1 -0
  357. package/dist/orchestration/voice-reply-policy.js +46 -0
  358. package/dist/orchestration/voice-reply-policy.js.map +1 -0
  359. package/dist/orchestration/worker-shares.d.ts +8 -0
  360. package/dist/orchestration/worker-shares.d.ts.map +1 -0
  361. package/dist/orchestration/worker-shares.js +59 -0
  362. package/dist/orchestration/worker-shares.js.map +1 -0
  363. package/dist/orchestration/workflow.d.ts +121 -0
  364. package/dist/orchestration/workflow.d.ts.map +1 -0
  365. package/dist/orchestration/workflow.js +69 -0
  366. package/dist/orchestration/workflow.js.map +1 -0
  367. package/dist/session/input-image.d.ts +10 -0
  368. package/dist/session/input-image.d.ts.map +1 -0
  369. package/dist/session/input-image.js +3 -0
  370. package/dist/session/input-image.js.map +1 -0
  371. package/dist/session/process.d.ts +15 -3
  372. package/dist/session/process.d.ts.map +1 -1
  373. package/dist/session/process.js +143 -35
  374. package/dist/session/process.js.map +1 -1
  375. package/dist/session/runtime-profile.d.ts +36 -0
  376. package/dist/session/runtime-profile.d.ts.map +1 -0
  377. package/dist/session/runtime-profile.js +63 -0
  378. package/dist/session/runtime-profile.js.map +1 -0
  379. package/dist/session/store.d.ts.map +1 -1
  380. package/dist/session/store.js +23 -1
  381. package/dist/session/store.js.map +1 -1
  382. package/dist/share/share-store.d.ts +3 -1
  383. package/dist/share/share-store.d.ts.map +1 -1
  384. package/dist/share/share-store.js +13 -2
  385. package/dist/share/share-store.js.map +1 -1
  386. package/dist/shared/line-request-order.d.ts +2 -0
  387. package/dist/shared/line-request-order.d.ts.map +1 -0
  388. package/dist/shared/line-request-order.js +16 -0
  389. package/dist/shared/line-request-order.js.map +1 -0
  390. package/dist/shared/task-elapsed.d.ts +3 -0
  391. package/dist/shared/task-elapsed.d.ts.map +1 -0
  392. package/dist/shared/task-elapsed.js +12 -0
  393. package/dist/shared/task-elapsed.js.map +1 -0
  394. package/dist/shared/task-updated-time.d.ts +3 -0
  395. package/dist/shared/task-updated-time.d.ts.map +1 -0
  396. package/dist/shared/task-updated-time.js +15 -0
  397. package/dist/shared/task-updated-time.js.map +1 -0
  398. package/dist/skills/loader.d.ts +2 -0
  399. package/dist/skills/loader.d.ts.map +1 -1
  400. package/dist/skills/loader.js.map +1 -1
  401. package/dist/telegram/chunks.d.ts +19 -0
  402. package/dist/telegram/chunks.d.ts.map +1 -0
  403. package/dist/telegram/chunks.js +129 -0
  404. package/dist/telegram/chunks.js.map +1 -0
  405. package/dist/telegram/markdown.d.ts.map +1 -1
  406. package/dist/telegram/markdown.js +18 -3
  407. package/dist/telegram/markdown.js.map +1 -1
  408. package/dist/telegram/receiver.d.ts +2 -1
  409. package/dist/telegram/receiver.d.ts.map +1 -1
  410. package/dist/telegram/receiver.js +6 -1
  411. package/dist/telegram/receiver.js.map +1 -1
  412. package/dist/types.d.ts +14 -0
  413. package/dist/types.d.ts.map +1 -1
  414. package/dist/ui/web-ui.d.ts.map +1 -1
  415. package/dist/ui/web-ui.js +31 -5
  416. package/dist/ui/web-ui.js.map +1 -1
  417. package/dist/voice/diagnostics.d.ts +29 -0
  418. package/dist/voice/diagnostics.d.ts.map +1 -0
  419. package/dist/voice/diagnostics.js +37 -0
  420. package/dist/voice/diagnostics.js.map +1 -0
  421. package/dist/voice/errors.d.ts +19 -0
  422. package/dist/voice/errors.d.ts.map +1 -0
  423. package/dist/voice/errors.js +133 -0
  424. package/dist/voice/errors.js.map +1 -0
  425. package/dist/voice/line-audio.d.ts +4 -0
  426. package/dist/voice/line-audio.d.ts.map +1 -0
  427. package/dist/voice/line-audio.js +27 -0
  428. package/dist/voice/line-audio.js.map +1 -0
  429. package/dist/voice/managed-quota.d.ts +5 -0
  430. package/dist/voice/managed-quota.d.ts.map +1 -0
  431. package/dist/voice/managed-quota.js +35 -0
  432. package/dist/voice/managed-quota.js.map +1 -0
  433. package/dist/voice/mp3.d.ts +3 -0
  434. package/dist/voice/mp3.d.ts.map +1 -0
  435. package/dist/voice/mp3.js +30 -0
  436. package/dist/voice/mp3.js.map +1 -0
  437. package/dist/voice/notes.d.ts +9 -0
  438. package/dist/voice/notes.d.ts.map +1 -0
  439. package/dist/voice/notes.js +68 -0
  440. package/dist/voice/notes.js.map +1 -0
  441. package/dist/voice/playback.d.ts +22 -0
  442. package/dist/voice/playback.d.ts.map +1 -0
  443. package/dist/voice/playback.js +52 -0
  444. package/dist/voice/playback.js.map +1 -0
  445. package/dist/voice/preview.d.ts +6 -0
  446. package/dist/voice/preview.d.ts.map +1 -0
  447. package/dist/voice/preview.js +44 -0
  448. package/dist/voice/preview.js.map +1 -0
  449. package/dist/voice/protocol.d.ts +10 -0
  450. package/dist/voice/protocol.d.ts.map +1 -0
  451. package/dist/voice/protocol.js +30 -0
  452. package/dist/voice/protocol.js.map +1 -0
  453. package/dist/voice/providers/cartesia-tts.d.ts +26 -0
  454. package/dist/voice/providers/cartesia-tts.d.ts.map +1 -0
  455. package/dist/voice/providers/cartesia-tts.js +95 -0
  456. package/dist/voice/providers/cartesia-tts.js.map +1 -0
  457. package/dist/voice/providers/deepgram-stt.d.ts +20 -0
  458. package/dist/voice/providers/deepgram-stt.d.ts.map +1 -0
  459. package/dist/voice/providers/deepgram-stt.js +66 -0
  460. package/dist/voice/providers/deepgram-stt.js.map +1 -0
  461. package/dist/voice/providers/elevenlabs-stt.d.ts +20 -0
  462. package/dist/voice/providers/elevenlabs-stt.d.ts.map +1 -0
  463. package/dist/voice/providers/elevenlabs-stt.js +92 -0
  464. package/dist/voice/providers/elevenlabs-stt.js.map +1 -0
  465. package/dist/voice/providers/elevenlabs-tts.d.ts +26 -0
  466. package/dist/voice/providers/elevenlabs-tts.d.ts.map +1 -0
  467. package/dist/voice/providers/elevenlabs-tts.js +82 -0
  468. package/dist/voice/providers/elevenlabs-tts.js.map +1 -0
  469. package/dist/voice/providers/encoded-audio-tts.d.ts +21 -0
  470. package/dist/voice/providers/encoded-audio-tts.d.ts.map +1 -0
  471. package/dist/voice/providers/encoded-audio-tts.js +175 -0
  472. package/dist/voice/providers/encoded-audio-tts.js.map +1 -0
  473. package/dist/voice/providers/fake.d.ts +39 -0
  474. package/dist/voice/providers/fake.d.ts.map +1 -0
  475. package/dist/voice/providers/fake.js +41 -0
  476. package/dist/voice/providers/fake.js.map +1 -0
  477. package/dist/voice/providers/gemini-live-stt.d.ts +21 -0
  478. package/dist/voice/providers/gemini-live-stt.d.ts.map +1 -0
  479. package/dist/voice/providers/gemini-live-stt.js +158 -0
  480. package/dist/voice/providers/gemini-live-stt.js.map +1 -0
  481. package/dist/voice/providers/gemini.d.ts +60 -0
  482. package/dist/voice/providers/gemini.d.ts.map +1 -0
  483. package/dist/voice/providers/gemini.js +124 -0
  484. package/dist/voice/providers/gemini.js.map +1 -0
  485. package/dist/voice/providers/model-ref.d.ts +9 -0
  486. package/dist/voice/providers/model-ref.d.ts.map +1 -0
  487. package/dist/voice/providers/model-ref.js +31 -0
  488. package/dist/voice/providers/model-ref.js.map +1 -0
  489. package/dist/voice/providers/openrouter.d.ts +39 -0
  490. package/dist/voice/providers/openrouter.d.ts.map +1 -0
  491. package/dist/voice/providers/openrouter.js +154 -0
  492. package/dist/voice/providers/openrouter.js.map +1 -0
  493. package/dist/voice/providers/paxalabs-stt.d.ts +12 -0
  494. package/dist/voice/providers/paxalabs-stt.d.ts.map +1 -0
  495. package/dist/voice/providers/paxalabs-stt.js +41 -0
  496. package/dist/voice/providers/paxalabs-stt.js.map +1 -0
  497. package/dist/voice/providers/paxalabs-tts.d.ts +23 -0
  498. package/dist/voice/providers/paxalabs-tts.d.ts.map +1 -0
  499. package/dist/voice/providers/paxalabs-tts.js +80 -0
  500. package/dist/voice/providers/paxalabs-tts.js.map +1 -0
  501. package/dist/voice/providers/recorded-stt.d.ts +34 -0
  502. package/dist/voice/providers/recorded-stt.d.ts.map +1 -0
  503. package/dist/voice/providers/recorded-stt.js +59 -0
  504. package/dist/voice/providers/recorded-stt.js.map +1 -0
  505. package/dist/voice/providers/registry.d.ts +10 -0
  506. package/dist/voice/providers/registry.d.ts.map +1 -0
  507. package/dist/voice/providers/registry.js +77 -0
  508. package/dist/voice/providers/registry.js.map +1 -0
  509. package/dist/voice/providers/socket.d.ts +10 -0
  510. package/dist/voice/providers/socket.d.ts.map +1 -0
  511. package/dist/voice/providers/socket.js +91 -0
  512. package/dist/voice/providers/socket.js.map +1 -0
  513. package/dist/voice/providers/upstream.d.ts +9 -0
  514. package/dist/voice/providers/upstream.d.ts.map +1 -0
  515. package/dist/voice/providers/upstream.js +51 -0
  516. package/dist/voice/providers/upstream.js.map +1 -0
  517. package/dist/voice/providers/voice-catalog.d.ts +17 -0
  518. package/dist/voice/providers/voice-catalog.d.ts.map +1 -0
  519. package/dist/voice/providers/voice-catalog.js +167 -0
  520. package/dist/voice/providers/voice-catalog.js.map +1 -0
  521. package/dist/voice/queue.d.ts +2 -0
  522. package/dist/voice/queue.d.ts.map +1 -0
  523. package/dist/voice/queue.js +6 -0
  524. package/dist/voice/queue.js.map +1 -0
  525. package/dist/voice/session.d.ts +86 -0
  526. package/dist/voice/session.d.ts.map +1 -0
  527. package/dist/voice/session.js +464 -0
  528. package/dist/voice/session.js.map +1 -0
  529. package/dist/voice/turn-manager.d.ts +27 -0
  530. package/dist/voice/turn-manager.d.ts.map +1 -0
  531. package/dist/voice/turn-manager.js +75 -0
  532. package/dist/voice/turn-manager.js.map +1 -0
  533. package/dist/voice/types.d.ts +78 -0
  534. package/dist/voice/types.d.ts.map +1 -0
  535. package/dist/voice/types.js +18 -0
  536. package/dist/voice/types.js.map +1 -0
  537. package/dist/voice/wav.d.ts +2 -0
  538. package/dist/voice/wav.d.ts.map +1 -0
  539. package/dist/voice/wav.js +21 -0
  540. package/dist/voice/wav.js.map +1 -0
  541. package/dist/wechat/ilink-client.d.ts +13 -0
  542. package/dist/wechat/ilink-client.d.ts.map +1 -1
  543. package/dist/wechat/ilink-client.js +19 -0
  544. package/dist/wechat/ilink-client.js.map +1 -1
  545. package/dist/whatsapp/manager.d.ts.map +1 -1
  546. package/dist/whatsapp/manager.js +51 -1
  547. package/dist/whatsapp/manager.js.map +1 -1
  548. package/mcp/capability-catalog.ts +206 -0
  549. package/mcp/modules.ts +56 -0
  550. package/mcp/server.ts +18 -37
  551. package/mcp/tools/browser/module.ts +1 -1
  552. package/mcp/tools/browser/skills/open-browser/SKILL.md +5 -3
  553. package/mcp/tools/discord/commands.ts +17 -1
  554. package/mcp/tools/discord/inbound.ts +11 -0
  555. package/mcp/tools/discord/module.ts +42 -2
  556. package/mcp/tools/discord/receiver-server.ts +10 -0
  557. package/mcp/tools/discord/types.ts +4 -1
  558. package/mcp/tools/image/module.ts +3 -1
  559. package/mcp/tools/receiver-spool.test.ts +28 -0
  560. package/mcp/tools/receiver-spool.ts +56 -0
  561. package/mcp/tools/share-file/module.ts +9 -1
  562. package/mcp/tools/shared/share-client.ts +19 -0
  563. package/mcp/tools/tasks/module.ts +35 -0
  564. package/mcp/tools/telegram/media-group.ts +21 -0
  565. package/mcp/tools/telegram/receiver-server.ts +232 -11
  566. package/mcp/tools/telegram/selection-confirmation.ts +20 -0
  567. package/mcp/tools/telegram/task-browser.ts +117 -0
  568. package/mcp/tools/telegram/task-detail.ts +36 -0
  569. package/mcp/tools/telegram/task-elapsed.ts +1 -0
  570. package/mcp/tools/telegram/typing.ts +6 -116
  571. package/mcp/tools/video/module.ts +1 -1
  572. package/mcp/types.ts +5 -0
  573. package/package.json +2 -1
  574. package/resource/claude_gateway.jpeg +0 -0
  575. package/scripts/orchestration/bench-store.cjs +30 -0
  576. package/scripts/orchestration/inspect.cjs +16 -0
  577. package/scripts/orchestration/probe-profile.cjs +67 -0
  578. package/scripts/orchestration/reconcile.cjs +21 -0
  579. package/scripts/orchestration/smoke-elevenlabs.cjs +90 -0
  580. package/scripts/orchestration/smoke-media.cjs +70 -0
  581. package/scripts/orchestration/smoke-text.cjs +101 -0
  582. package/scripts/orchestration/smoke-vision.cjs +49 -0
@@ -0,0 +1,56 @@
1
+ import { mediaGroupKey, mergeMediaGroup, type ChannelInput } from './telegram/media-group';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { mkdirSync, readdirSync, openSync, closeSync, writeFileSync, fsyncSync, renameSync, readFileSync, unlinkSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ /** Receiver-local handoff journal. Persist before returning to the platform
7
+ * client; only remove after the gateway confirms its SQLite admission. */
8
+ export class ReceiverSpool {
9
+ private active = false;
10
+ private readonly timer: ReturnType<typeof setInterval>;
11
+ constructor(private readonly directory: string, private readonly callback: string, private readonly request: typeof fetch = fetch, private readonly mediaGroupWaitMs = 2000) {
12
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
13
+ this.timer = setInterval(() => { void this.flush(); }, 1000); this.timer.unref();
14
+ void this.flush();
15
+ }
16
+ enqueue(input: unknown): void {
17
+ const group=mediaGroupKey(input as ChannelInput);
18
+ if(group){
19
+ let prior:ChannelInput|undefined;
20
+ try{prior=JSON.parse(readFileSync(join(this.directory,`${group}.json`),'utf8'));}catch{}
21
+ input=mergeMediaGroup(prior,input as ChannelInput);
22
+ }
23
+ const payload = JSON.stringify(input);
24
+ if (Buffer.byteLength(payload) > 131072) throw new Error('Ingress payload too large');
25
+ if (readdirSync(this.directory).filter(file => file.endsWith('.json')).length >= 1000) throw new Error('Ingress spool full');
26
+ const key = group ?? createHash('sha256').update(payload).digest('hex');
27
+ const temporary = join(this.directory, `${key}.${randomUUID()}.tmp`);
28
+ const descriptor = openSync(temporary, 'wx', 0o600);
29
+ try { writeFileSync(descriptor, payload); fsyncSync(descriptor); } finally { closeSync(descriptor); }
30
+ renameSync(temporary, join(this.directory, `${key}.json`));
31
+ this.syncDirectory();
32
+ void this.flush();
33
+ }
34
+ private syncDirectory(): void {
35
+ const descriptor = openSync(this.directory, 'r');
36
+ try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
37
+ }
38
+ async flush(): Promise<void> {
39
+ if (this.active) return;
40
+ this.active = true;
41
+ try {
42
+ const files = readdirSync(this.directory).filter(file => /^[a-f0-9]{64}\.json$/.test(file)).map(file => ({ file, created: statSync(join(this.directory, file)).mtimeMs })).sort((a, b) => a.created - b.created);
43
+ for (const { file } of files.slice(0, 100)) {
44
+ const location = join(this.directory, file);
45
+ const payload=readFileSync(location,'utf8');
46
+ const input=JSON.parse(payload) as ChannelInput;
47
+ if(mediaGroupKey(input)&&Date.now()-statSync(location).mtimeMs<this.mediaGroupWaitMs)continue;
48
+ const response = await this.request(this.callback, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, signal: AbortSignal.timeout(10000) });
49
+ if (!response.ok) break;
50
+ if(readFileSync(location,'utf8')===payload){unlinkSync(location); this.syncDirectory();}
51
+ }
52
+ } catch { /* persisted message is retried on next tick or process restart */ }
53
+ finally { this.active = false; }
54
+ }
55
+ close(): void { clearInterval(this.timer); }
56
+ }
@@ -28,7 +28,15 @@ export class ShareFileModule implements ToolModule {
28
28
  }
29
29
 
30
30
  getTools(): McpToolDefinition[] {
31
- return shareFileToolDefs;
31
+ if (process.env.GATEWAY_ORCHESTRATION_ROLE !== 'worker') return shareFileToolDefs;
32
+ return shareFileToolDefs.map(tool => ({
33
+ ...tool,
34
+ description: tool.description + ' In a task, use the original absolute file path in the active task workspace, a supplied input attachment or this sessionโ€™s media directory. The gateway stages it automatically; do not copy it to the agent-wide media root. Describe this as creating a link (Thai: เธชเธฃเน‰เธฒเธ‡เธฅเธดเธ‡เธเนŒ).',
35
+ inputSchema: { ...shareFileInputSchema, properties: { ...shareFileInputSchema.properties,
36
+ path: { type: 'string', description: 'Original absolute file path in the active task workspace or session media, or artifact:<id>.' },
37
+ paths: { type: 'array', items: { type: 'string' }, description: 'Up to 5 authorized absolute file paths or artifact refs.' },
38
+ } },
39
+ }));
32
40
  }
33
41
 
34
42
  async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
@@ -71,6 +71,7 @@ export class ShareClientError extends Error {
71
71
  * is absent the private mint endpoint is not mounted and calls fail closed.
72
72
  */
73
73
  export function shareBridgeEnabled(): boolean {
74
+ if (process.env.GATEWAY_ORCHESTRATION_ROLE === 'worker' && process.env.GATEWAY_ORCHESTRATION_MEDIA === 'true' && process.env.GATEWAY_ORCHESTRATION_TICKET_FILE) return true;
74
75
  return (
75
76
  !!process.env.GATEWAY_API_URL &&
76
77
  !!process.env.GATEWAY_API_KEY &&
@@ -88,6 +89,24 @@ async function callGateway(
88
89
  pathname: string,
89
90
  body?: Record<string, unknown>,
90
91
  ): Promise<{ status: number; json: Record<string, unknown> }> {
92
+ if (process.env.GATEWAY_ORCHESTRATION_ROLE === 'worker' && process.env.GATEWAY_ORCHESTRATION_TICKET_FILE) {
93
+ const { readFile } = await import('node:fs/promises');
94
+ const ticket = JSON.parse(await readFile(process.env.GATEWAY_ORCHESTRATION_TICKET_FILE, 'utf8'));
95
+ const response = await fetch(ticket.url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ticket.token}` },
96
+ body: JSON.stringify({ tool: 'task_share_call', action_id: crypto.randomUUID(), args: { method, pathname, body } }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
97
+ if (!response.ok) {
98
+ const failure = await response.json().catch(() => ({})) as Record<string, unknown> | null;
99
+ // The bridge uses {error: CODE}; retain that cause instead of treating
100
+ // path validation, stale attempts and unavailable configuration alike.
101
+ const code = typeof failure?.error === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(failure.error)
102
+ ? failure.error : 'share_scope_denied';
103
+ const error = code === 'ARTIFACT_PATH_DENIED'
104
+ ? 'File is outside this task scope. Use the original fileโ€™s absolute path in the active task workspace, a supplied input attachment, or this sessionโ€™s media directory. Do not copy it to the agent-wide media root. The gateway stages authorized files automatically.'
105
+ : code === 'share_scope_denied' ? 'Scoped share request denied' : code;
106
+ return { status: response.status, json: { error, code } };
107
+ }
108
+ return await response.json() as { status: number; json: Record<string, unknown> };
109
+ }
91
110
  const res = await fetch(`${apiBase()}${pathname}`, {
92
111
  method,
93
112
  headers: {
@@ -0,0 +1,35 @@
1
+ // MCP sources are published, src/ is not. Share the compiled schema so an
2
+ // installed package has the same inventory as a development checkout.
3
+ import { WORKFLOW_SCHEMA } from '../../../dist/orchestration/workflow.js';
4
+ import type { McpToolDefinition, McpToolResult } from '../../types';
5
+
6
+ const schema = (properties: Record<string, unknown>, required: string[]) => ({ type: 'object', properties, required, additionalProperties: false });
7
+ const text = { type: 'string' };
8
+ export const AGENT_TASK_TOOLS: McpToolDefinition[] = [
9
+ { name: 'capabilities_list', description: 'Discover all capabilities this agent can offer: installed/shared/native skills, gateway MCP tools and enabled connector/plugin MCP tools, including tools executed only by workers. Read metadata only; never executes a tool or grants permissions. Use query to find relevant tools before assigning work. Follow next_offset until null when the user asks for the complete list, passing catalog_version from the first page. On CAPABILITY_CATALOG_CHANGED restart from offset 0. Report unavailable discovery honestly; do not confuse your own restricted callable tools with the capabilities of the agent as a whole.', inputSchema: schema({query:text,catalog_version:text,offset:{type:'integer',minimum:0}},[]) },
10
+ { name: 'conversation_intake', description: 'Prepare executable work, incomplete materials, or an amendment to an existing task. Do not call this for greetings, introductions, casual conversation or questions you can answer directly; answer those normally once. When semantic intake is enabled, acknowledge before work mutations. Incomplete material is inspected now; the gateway asks the supplied clarification after the configured silence interval, not on every item.',
11
+ inputSchema: schema({mode:{type:'string',enum:['ready','wait','update']},acknowledgement:{type:'string',description:'A brief contextual message addressed directly to the user about the work you will do. Immediately displayed and spoken aloud. Never include private classification, reasoning, or instructions to yourself.'},preparation:text,clarification:text,task_id:text},['mode']) },
12
+ { name: 'task_spawn', description: 'Durably queue a task for an independent worker. Returns after commit without waiting for worker startup or completion.',
13
+ inputSchema: schema({ title: text, instructions: text, target_profile: text, spoken_acknowledgement: { type: 'string', description: 'Required for live voice: a short natural response in the user language explaining the specific work you are about to do. Describe the specific user request as your own task, in the voice of your existing persona; do not announce handing it to a worker or use a generic acknowledgement. Speak as the person doing the work; avoid Thai delegation phrases such as เธชเธฑเนˆเธ‡เน€เธŠเน‡เธ„เนเธฅเน‰เธง, เธชเธฑเนˆเธ‡เน„เธ›เนเธฅเน‰เธง or เธชเนˆเธ‡เนƒเธซเน‰เธ•เธฃเธงเธˆเนเธฅเน‰เธง. Describe queued work as accepted/to do next, and only say it is running when that state is confirmed. Do not claim completion. The gateway manages playback and channel text-before-voice ordering.' }, skill_name: { type: 'string', description: 'Exact installed gateway or discovered Claude Code runtime skill name for skill-worker. Native skills are invoked directly by the CLI; shared/workspace skills are supplied with their resources. Never supply a file path.' }, skill_args: { type: 'string', description: 'Arguments for the installed skill; required with skill_name.' }, continue_task_id: { type: 'string', description: 'Prior task ID in this conversation for related follow-up work. Reuses its worker session when compatible; waits for success by default. Queue every authorized dependent step now, using each returned task ID for the next step. Omit for independent work.' }, continuation_policy: { type: 'string', enum: ['after_success', 'after_terminal'], description: 'With continue_task_id: after_success (default) starts only after the predecessor succeeds; otherwise fails without executing. Use after_terminal only for explicitly authorized recovery/inspection that must run after failure or cancellation.' }, context_refs: { type: 'array', items: text, description: 'Only orchestration input IDs, authorized attachment refs or task artifact IDs. Put project filenames in instructions instead.' } }, ['title', 'instructions', 'target_profile']) },
14
+ { name: 'task_status', description: 'Read the task index in this conversation without waiting. Pass task_id to retrieve the complete stored result and evidence before discussing an earlier task. An index entry is not a partial worker report.', inputSchema: schema({ task_id: text }, []) },
15
+ { name: 'task_cancel', description: 'Request cancellation, including needs_reconciliation cleanup. cancel_requested is not proof of termination. Existing files are retained. Set replaced_by_task_id only for a known replacement task, never infer from similar titles.', inputSchema: schema({ task_id: text, replaced_by_task_id: text }, ['task_id']) },
16
+ { name: 'task_update', description: 'Accept a revised task instruction. applied_revision changes only when the worker accepts it.', inputSchema: schema({ task_id: text, expected_revision: { type: 'integer', minimum: 1 }, instruction: text, mode: { type: 'string', enum: ['when_ready', 'interrupt_and_resume'] } }, ['task_id', 'expected_revision', 'instruction', 'mode']) },
17
+ { name: 'task_answer', description: 'Answer the matching pending question on a task.', inputSchema: schema({ task_id: text, question_id: text, answer: text }, ['task_id', 'question_id', 'answer']) },
18
+ ];
19
+ export const WORKER_REPORT_TOOLS: McpToolDefinition[] = [
20
+ { name: 'task_memory_append', description: 'Append an explicitly requested memory note for a channel task. API tasks cannot write memory. Files are restricted to MEMORY.md, USER.md, or memory/<name>.md. Retains existing content and deduplicates retries.', inputSchema: schema({ note: text, path: text }, ['note']) },
21
+ { name: 'task_stage_file', description: 'Stage a finished image or document for the user. The orchestration attaches it to the next completed agent response after this task succeeds, using the original destination. This tool does not send to a channel. Call once per output file. For an image returned by an MCP tool (such as a remote browser screenshot), omit path to use the latest captured image in this attempt, or pass source_tool_call_id to select its tool call. The gateway saves the actual image bytes; never invent a screenshot filename. For an existing local file supply path.', inputSchema: schema({ path: text, source_tool_call_id: text, caption: text }, []) },
22
+ { name: 'task_report_progress', description: 'Report factual progress internally. At phase changes include a checkpoint with phase, evidenceVersion, checks, findings and nextAction. This does not send a user message.', inputSchema: schema({ text, checkpoint: WORKFLOW_SCHEMA }, ['text']) },
23
+ { name: 'task_request_input', description: 'Ask the user for information required by your task, then end this turn immediately.', inputSchema: schema({ question: text }, ['question']) },
24
+ ];
25
+
26
+ export async function callTaskBridge(tool: string, args: Record<string, unknown>, requestId: string, signal: AbortSignal): Promise<McpToolResult> {
27
+ // Rotated by the gateway at each decision/attempt boundary. A loaded token is
28
+ // immutable for this request; late requests cannot borrow the next decision.
29
+ const { readFile } = await import('node:fs/promises');
30
+ const scope = JSON.parse(await readFile(process.env.GATEWAY_ORCHESTRATION_TICKET_FILE!, 'utf8')) as { url: string; token: string };
31
+ const response = await fetch(scope.url, { method: 'POST', signal, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${scope.token}` },
32
+ body: JSON.stringify({ tool, args, action_id: requestId }) });
33
+ const body = await response.text();
34
+ return { content: [{ type: 'text', text: body }], ...(response.ok ? {} : { isError: true }) };
35
+ }
@@ -0,0 +1,21 @@
1
+ import { createHash } from 'node:crypto';
2
+ export type ChannelInput = {content:string;meta:Record<string,string>};
3
+ export function mediaGroupKey(input: ChannelInput): string | undefined {
4
+ const m=input?.meta;
5
+ return m?.media_group_id ? createHash('sha256').update(JSON.stringify([m.chat_id,m.user_id,m.message_thread_id??'',m.media_group_id])).digest('hex') : undefined;
6
+ }
7
+ /** Telegram albums arrive as separate updates. Keep the native group intact. */
8
+ export function mergeMediaGroup(prior:ChannelInput|undefined,input:ChannelInput):ChannelInput {
9
+ const ids:string[]=prior?JSON.parse(prior.meta.message_ids_json):[];
10
+ if(ids.includes(input.meta.message_id))return prior!;
11
+ if(ids.length>=10)throw Error('Telegram media group exceeds 10 items');
12
+ const parts:Array<{id:string;caption:string;ref?:string;path?:string;name?:string}> = prior?JSON.parse(prior.meta.media_group_parts_json):[];
13
+ parts.push({id:input.meta.message_id,caption:input.meta.media_caption??'',ref:input.meta.attachment_file_id,path:input.meta.image_path,name:input.meta.attachment_name});
14
+ parts.sort((a,b)=>Number(a.id)-Number(b.id));
15
+ const meta:Record<string,string>={...(prior?.meta??input.meta),media_group_id:input.meta.media_group_id,message_id:parts[0]!.id,
16
+ message_ids_json:JSON.stringify(parts.map(p=>p.id)),media_group_parts_json:JSON.stringify(parts),
17
+ attachments_json:JSON.stringify(parts.map(({ref,path,name})=>({ref,path,name})))};
18
+ // Album files must not turn a mixed album into a single-file voice-note turn.
19
+ delete meta.attachment_file_id;delete meta.image_path;delete meta.attachment_kind;
20
+ return {content:parts.map(p=>p.caption).filter(Boolean).join('\n\n')||'[Media album attached]',meta};
21
+ }
@@ -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
- `/cli โ€” open the live terminal viewer\n\n` +
1179
+ (ORCHESTRATION_ENABLED ? `/tasks โ€” view 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
+ }