@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
@@ -35,6 +35,14 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AgentRunner = exports.CHANNEL_COALESCE_WINDOW_MS = exports.WHATSAPP_CLOUD_WINDOW_NOTE = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
37
37
  exports.toRelMediaFiles = toRelMediaFiles;
38
+ const channel_input_media_1 = require("../orchestration/channel-input-media");
39
+ const chunks_1 = require("../telegram/chunks");
40
+ const file_delivery_1 = require("../orchestration/file-delivery");
41
+ const voice_reply_policy_1 = require("../orchestration/voice-reply-policy");
42
+ const gateway_config_1 = require("../orchestration/gateway-config");
43
+ const delivery_1 = require("../orchestration/delivery");
44
+ const control_delivery_1 = require("../orchestration/control-delivery");
45
+ const stop_controls_1 = require("../orchestration/stop-controls");
38
46
  const crypto_1 = require("crypto");
39
47
  const events_1 = require("events");
40
48
  const fs = __importStar(require("fs"));
@@ -54,6 +62,10 @@ const slack_client_1 = require("../api/slack-client");
54
62
  const whatsapp_cloud_client_1 = require("../api/whatsapp-cloud-client");
55
63
  const manager_1 = require("../whatsapp/manager");
56
64
  const whatsapp_accounts_1 = require("../config/whatsapp-accounts");
65
+ const manager_2 = require("../wechat/manager");
66
+ const ilink_client_1 = require("../wechat/ilink-client");
67
+ const wechat_access_1 = require("../api/wechat-access");
68
+ const pending_senders_1 = require("../api/pending-senders");
57
69
  const markdown_1 = require("../telegram/markdown");
58
70
  const skills_1 = require("../skills");
59
71
  const builtin_commands_1 = require("./builtin-commands");
@@ -71,6 +83,7 @@ const screen_1 = require("../shell/screen");
71
83
  const db_1 = require("../history/db");
72
84
  const media_store_1 = require("../history/media-store");
73
85
  const cleanup_1 = require("../history/cleanup");
86
+ const types_1 = require("../history/types");
74
87
  const knowledge_1 = require("./knowledge");
75
88
  const resolve_1 = require("../connectors/resolve");
76
89
  const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
@@ -221,6 +234,229 @@ class AgentRunner extends events_1.EventEmitter {
221
234
  channelFor(chatId) {
222
235
  return this.channelSourceMap.get(chatId) ?? 'telegram';
223
236
  }
237
+ requiresDurableChannelIngress(source) {
238
+ return Boolean((this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(source)) ||
239
+ this.orchestration?.ownsChannelIngress(source));
240
+ }
241
+ orchestrationForApi(sessionId) {
242
+ return Boolean((this.orchestration?.ownsSession(sessionId) && !this.orchestration.canReturnToLegacy()) ||
243
+ (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api')));
244
+ }
245
+ async sendLinkedOrchestrationChannel(...[binding, text, _id, file]) {
246
+ const channel = String(binding.channel), chat = String(binding.chat_id);
247
+ if (channel === 'wechat' && file)
248
+ return { state: 'failed', code: 'CHANNEL_ATTACHMENTS_UNSUPPORTED' };
249
+ let path;
250
+ if (file) {
251
+ try {
252
+ path = (0, file_delivery_1.resolveChannelFile)(this.agentConfig, file).path;
253
+ }
254
+ catch {
255
+ return { state: 'failed', code: 'ATTACHMENT_UNAVAILABLE' };
256
+ }
257
+ }
258
+ try {
259
+ if (channel === 'whatsapp') {
260
+ // Persist the receiving number in the binding; an in-memory last-chat map
261
+ // can change while a worker is running or disappear after a restart.
262
+ const thread = String(binding.thread_key ?? '');
263
+ const account = thread.startsWith('whatsapp-account:') ? thread.slice('whatsapp-account:'.length) : undefined;
264
+ await this.sendWhatsAppMessage(chat, file?.caption || text, path, account, { asDocument: !!file && file.kind !== 'image' });
265
+ }
266
+ else if (channel === 'wechat')
267
+ await this.sendWeChatMessage(chat, text);
268
+ else
269
+ return { state: 'failed', code: 'DELIVERY_NOT_CONFIGURED' };
270
+ return { state: 'delivered' };
271
+ }
272
+ catch {
273
+ return { state: 'unknown', code: 'PROVIDER_RECEIPT_UNKNOWN' };
274
+ }
275
+ }
276
+ async sendOrchestrationControl(channel, chatId, menu, meta) {
277
+ if (!['whatsapp', 'whatsapp_cloud', 'wechat'].includes(channel))
278
+ return (0, control_delivery_1.sendControlMenu)(this.agentConfig, channel, chatId, menu, meta);
279
+ const text = [menu.text, ...menu.buttons.map(button => `${button.label}: /orch ${button.data.replace(/^orch:/, '')}`)].join('\n');
280
+ const send = (0, delivery_1.channelSender)(() => this.agentConfig, fetch, () => false, (...args) => this.sendLinkedOrchestrationChannel(...args));
281
+ for (const part of (0, chunks_1.chunkText)(text, 1900)) {
282
+ const result = await send({ channel, chat_id: chatId, thread_key: channel === 'whatsapp' && meta.account_id ? `whatsapp-account:${meta.account_id}` : '' }, part, (0, crypto_1.randomUUID)());
283
+ if (result.state !== 'delivered')
284
+ throw new Error(result.code);
285
+ }
286
+ }
287
+ async getOrchestration() {
288
+ if (this.orchestration)
289
+ return this.orchestration;
290
+ if (!this.orchestrationStarting) {
291
+ this.orchestrationStarting = Promise.resolve().then(() => __importStar(require('../orchestration/runtime'))).then(({ AgentOrchestrationRuntime }) => AgentOrchestrationRuntime.open(this.agentConfig, this.gatewayConfig, this.agentDir, this.sessionStore, this.historyDb, {
292
+ sendLinkedChannel: (...args) => this.sendLinkedOrchestrationChannel(...args),
293
+ skills: () => this.skillRegistry,
294
+ refreshSkills: async () => {
295
+ try {
296
+ const { discoverCliSkills } = await Promise.resolve().then(() => __importStar(require('../orchestration/cli-skills')));
297
+ this.skillRegistry.cliSkills = await discoverCliSkills(this.agentConfig, undefined, this.gatewayConfig);
298
+ delete this.skillRegistry.cliDiscoveryError;
299
+ }
300
+ catch {
301
+ this.skillRegistry.cliSkills = [];
302
+ this.skillRegistry.cliDiscoveryError = 'CLI_SKILL_DISCOVERY_UNAVAILABLE';
303
+ }
304
+ },
305
+ onManagedTurn: (sessionId, text, metrics, skills) => {
306
+ const key = `orchestration:${sessionId}`;
307
+ this.skillLearning?.onTurnStart(key, sessionId, text, skills, metrics.startedAt);
308
+ for (const id of metrics.toolIds)
309
+ this.skillLearning?.onToolUse(key, id);
310
+ this.skillLearning?.onTokenUsage(key, metrics.inputTokens, metrics.totalTokens);
311
+ this.skillLearning?.onTurnEnd(key, sessionId);
312
+ },
313
+ createAgentSession: async (sessionId, profile, model, scope) => {
314
+ const key = scope && scope.source !== 'api' ? scope.chatId : sessionId;
315
+ const old = this.sessions.get(key);
316
+ if (old) {
317
+ if (!old.isIdle(0))
318
+ throw Object.assign(new Error('Agent session is busy'), { code: 'CONFLICT' });
319
+ await old.stop();
320
+ this.sessions.delete(key);
321
+ }
322
+ if (this.sessions.size >= this.maxConcurrent)
323
+ throw new Error('Session pool full');
324
+ if (this.agentConfig.type !== 'app-agent')
325
+ (0, knowledge_1.spawnArchiveReindex)(this.agentConfig.workspace, this.agentConfig.knowledge, this.gatewayConfig.gateway.knowledge);
326
+ const agentSession = new process_1.SessionProcess(sessionId, scope?.source ?? 'api', this.agentConfig, this.gatewayConfig, this.sessionStore, scope?.chatId, profile);
327
+ agentSession.historyLimit = (0, process_1.resolveMaxHistoryMessages)(this.agentConfig.history?.maxHistoryMessages, this.gatewayConfig.gateway.history?.maxHistoryMessages);
328
+ if (model)
329
+ agentSession.modelOverride = model;
330
+ this.sessions.set(key, agentSession);
331
+ return agentSession;
332
+ },
333
+ releaseAgentSession: async (sessionId, agentSession) => {
334
+ await agentSession.stop();
335
+ for (const [key, value] of this.sessions)
336
+ if (value === agentSession)
337
+ this.sessions.delete(key);
338
+ },
339
+ })).then(orchestration => { this.orchestration = orchestration; return orchestration; }).catch(error => { this.orchestrationStarting = undefined; throw error; });
340
+ }
341
+ return this.orchestrationStarting;
342
+ }
343
+ async orchestrationActivity(sessionId, principalId, after) {
344
+ return (await this.getOrchestration()).activity(sessionId, principalId, after);
345
+ }
346
+ async subscribeResponseText(sessionId, principalId, receive) {
347
+ return (await this.getOrchestration()).subscribeText(sessionId, principalId, receive);
348
+ }
349
+ async voiceReplay(sessionId, principalId, responseId) {
350
+ return (await this.getOrchestration()).voiceAudio(sessionId, principalId, responseId);
351
+ }
352
+ saveVoiceReplay(sessionId, principalId, responseId, audio) {
353
+ this.orchestration?.saveVoiceAudio(sessionId, principalId, responseId, audio);
354
+ }
355
+ async authorizeVoiceSession(sessionId, principalId) {
356
+ (await this.getOrchestration()).authorizeSession(sessionId, principalId);
357
+ }
358
+ async subscribeVoiceResults(sessionId, principalId, receive, gender) {
359
+ return (await this.getOrchestration()).subscribeVoiceResults(sessionId, principalId, receive, gender);
360
+ }
361
+ recordVoicePlayback(responseId, principalId, progress, state) {
362
+ this.orchestration?.recordPlayback(responseId, principalId, progress, state);
363
+ }
364
+ async submitVoiceUtterance(sessionId, chatId, principalId, text, utteranceId, allowTools, model) {
365
+ if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.voice?.enabled)
366
+ throw new Error('Voice is disabled');
367
+ if (!(await this.apiSessionExists(chatId, sessionId)))
368
+ throw new Error('Session not found');
369
+ const orchestration = await this.getOrchestration();
370
+ const accepted = orchestration.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: 'api', accountId: principalId,
371
+ chatId, threadKey: '', principalId }, text, model, modality: 'live_voice', ingressKey: `utterance:${utteranceId}` }, { execute: allowTools, writeMemory: false });
372
+ // The browser refreshes history on utterance.accepted, before inference finishes.
373
+ await orchestration.flushHistory();
374
+ return { ...accepted, responseId: () => orchestration.responseIdForInput(accepted.inputId) };
375
+ }
376
+ async listApiTasks(sessionId, principalId, page = 0, pageSize = 10, includeFinished = false) {
377
+ if (!this.agentConfig.orchestration?.enabled)
378
+ throw new Error('ORCHESTRATION_DISABLED');
379
+ const runtime = await this.getOrchestration();
380
+ runtime.authorizeSession(sessionId, principalId);
381
+ return runtime.taskControls.list(sessionId, principalId, page, pageSize, includeFinished);
382
+ }
383
+ async showApiTask(sessionId, principalId, taskId) {
384
+ if (!this.agentConfig.orchestration?.enabled)
385
+ throw new Error('ORCHESTRATION_DISABLED');
386
+ const runtime = await this.getOrchestration();
387
+ const detail = runtime.taskControls.detail(sessionId, principalId, taskId);
388
+ const task = runtime.store.task(taskId);
389
+ // Full retained result, with worker-only skill context removed by status().
390
+ return { ...runtime.tasks.status(task.conversationId, principalId, taskId)[0], ...detail };
391
+ }
392
+ async cancelApiTask(sessionId, principalId, taskId) {
393
+ if (!this.agentConfig.orchestration?.enabled)
394
+ throw new Error('ORCHESTRATION_DISABLED');
395
+ const runtime = await this.getOrchestration();
396
+ const task = runtime.taskControls.cancel(sessionId, principalId, taskId);
397
+ return task;
398
+ }
399
+ stopVoiceResponse(sessionId) { this.orchestration?.stopResponse(sessionId); }
400
+ async sendOrchestratedApi(sessionId, chatId, message, opts, onText, onTool) {
401
+ const semantic = this.agentConfig.orchestration?.conversation?.semanticIntake === true;
402
+ if (!semantic && this.pendingApiSessions.has(sessionId))
403
+ throw Object.assign(new Error('Session already has a pending request'), { code: 'CONFLICT' });
404
+ if (!opts.principalId)
405
+ throw new Error('Authenticated principal required for conversation orchestration');
406
+ const deadline = Date.now() + opts.timeoutMs;
407
+ if (!semantic)
408
+ this.pendingApiSessions.add(sessionId); // reserve before the first await
409
+ try {
410
+ const orchestration = await this.getOrchestration();
411
+ await this.sessionStore.ensureApiSession(this.agentConfig.id, chatId, sessionId);
412
+ this.apiChatIds.set(sessionId, chatId);
413
+ const media = opts.mediaFiles?.length ? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger) : [];
414
+ const index = await this.sessionStore.loadIndex(this.agentConfig.id, chatId, 'api');
415
+ const saved = index?.sessions.find(session => session.id === sessionId)?.imageConfig;
416
+ const selected = { ...(saved ? AgentRunner.durableImageConfig(saved) : {}), ...opts.imageParams };
417
+ const imageParams = AgentRunner.remapImageParamsRefs(Object.keys(selected).length ? selected : undefined, opts.mediaFiles, media);
418
+ const videoParams = AgentRunner.remapVideoParamsRefs(opts.videoParams, opts.mediaFiles, media);
419
+ if (videoParams)
420
+ await this.sessionStore.updateSessionMeta(this.agentConfig.id, chatId, sessionId, { videoConfig: videoParams }, 'api');
421
+ const imageConfig = opts.imageParams && imageParams ? AgentRunner.durableImageConfig(imageParams) : undefined;
422
+ if (imageConfig)
423
+ await this.sessionStore.updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig }, 'api');
424
+ const requestId = opts.requestId ?? (0, crypto_1.randomUUID)();
425
+ const scopedInput = { scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: 'api', accountId: opts.principalId,
426
+ chatId, threadKey: '', principalId: opts.principalId }, text: message || '[Attachment inspection requested]', attachmentIds: media,
427
+ requestId, storeUserMessage: !opts.skipUserMessage,
428
+ metadata: imageParams || videoParams ? { promptContext: (imageParams ? AgentRunner.buildImageParamsNote(imageParams) : '') + (videoParams ? AgentRunner.buildVideoParamsNote(videoParams) : ''), imageRefs: imageParams?.image_refs } : undefined };
429
+ let text;
430
+ if (semantic) {
431
+ const accepted = orchestration.submitInput({ ...scopedInput, model: opts.model }, { execute: opts.allowTools ?? false, writeMemory: false }, onTool);
432
+ const lengths = new Map();
433
+ const unsubscribe = orchestration.subscribeText(sessionId, opts.principalId, event => {
434
+ if (!orchestration.responseBelongsToInput(accepted.inputId, event.responseId) || !event.text)
435
+ return;
436
+ const before = lengths.get(event.responseId) ?? 0;
437
+ if (event.text.length > before)
438
+ onText?.(event.text.slice(before));
439
+ lengths.set(event.responseId, event.text.length);
440
+ });
441
+ try {
442
+ text = await accepted.response;
443
+ }
444
+ finally {
445
+ unsubscribe();
446
+ }
447
+ }
448
+ else
449
+ text = await orchestration.send(scopedInput, { execute: opts.allowTools ?? false, writeMemory: false }, { timeoutMs: opts.timeoutMs, model: opts.model, onText, onTool });
450
+ if (opts.waitForTasks)
451
+ text = await orchestration.waitForTaskReport(sessionId, opts.principalId, requestId, text, deadline);
452
+ this.addApiAttachments(sessionId, orchestration.responseFiles(sessionId, requestId).map(ref => media_store_1.MediaStore.resolvePath(this.agentsBaseDir, this.agentConfig.id, ref)));
453
+ return { text, attachments: this.popApiAttachments(sessionId) };
454
+ }
455
+ finally {
456
+ if (!semantic)
457
+ this.pendingApiSessions.delete(sessionId);
458
+ }
459
+ }
224
460
  constructor(agentConfig, gatewayConfig, logger) {
225
461
  super();
226
462
  this.stopping = false;
@@ -241,6 +477,7 @@ class AgentRunner extends events_1.EventEmitter {
241
477
  this.slackThreadTs = new Map();
242
478
  this.receiver = null;
243
479
  this.discordReceiver = null;
480
+ this.discordCommandConfig = '';
244
481
  // LINE slow-LLM postback button manager (null when LINE disabled or threshold=0).
245
482
  this.lineReply = null;
246
483
  // Slack has no reply-token TTL to work around (see the plan's "no reply-manager
@@ -271,6 +508,12 @@ class AgentRunner extends events_1.EventEmitter {
271
508
  // that omits account_id) answer on the SAME number the user wrote to
272
509
  // instead of guessing 'default' and replying from the wrong line.
273
510
  this.whatsappAccountForChat = new Map();
511
+ // WeChat: unlike every credential-input channel above, constructed
512
+ // unconditionally (there's no config field to gate on — the link flow is
513
+ // QR device-pairing, not a typed-in token, same reasoning WhatsApp/Baileys
514
+ // uses). Does nothing until startWeChatLinking() is called or a
515
+ // previously-linked session is resumed at start().
516
+ this.wechat = null;
274
517
  this.idleCleanerTimer = null;
275
518
  // Tracks session IDs with an in-flight API request (prevents concurrent turns)
276
519
  this.pendingApiSessions = new Set();
@@ -346,6 +589,7 @@ class AgentRunner extends events_1.EventEmitter {
346
589
  this.apiChatIds = new Map();
347
590
  // Skill registry for detecting /skill-name commands in user messages
348
591
  this.skillRegistry = { skills: new Map() };
592
+ this.skillNotificationOrigins = new Map();
349
593
  // Cancel function for the daily history cleanup timer
350
594
  this.cancelCleanup = null;
351
595
  /**
@@ -388,7 +632,8 @@ class AgentRunner extends events_1.EventEmitter {
388
632
  }
389
633
  });
390
634
  };
391
- this.agentConfig = agentConfig;
635
+ this.telegramCommandConfig = '';
636
+ this.agentConfig = (0, gateway_config_1.applyGatewayOrchestration)(agentConfig, gatewayConfig);
392
637
  this.gatewayConfig = gatewayConfig;
393
638
  this.logger = logger ?? (0, logger_1.createLogger)(agentConfig.id, gatewayConfig.gateway.logDir);
394
639
  // Resolve agentsBaseDir: workspace is at <agentsBaseDir>/<agentId>/workspace
@@ -417,6 +662,25 @@ class AgentRunner extends events_1.EventEmitter {
417
662
  return this.skillRegistry;
418
663
  }
419
664
  /** Wire the skill-learning manager (planning-62). Optional; unset = feature off. */
665
+ async notifySkillLearning(sessionId, text) {
666
+ if (this.orchestration?.ownsSession(sessionId)) {
667
+ const conversation = this.orchestration.store.get('SELECT source,chat_id FROM conversations WHERE agent_session_id=?', sessionId);
668
+ const source = conversation?.source;
669
+ const index = source && source !== 'api' ? await this.sessionStore.loadIndex(this.agentConfig.id, String(conversation.chat_id), source) : undefined;
670
+ await this.orchestration.notifySession(sessionId, text, source === 'api' || index?.activeSessionId === sessionId);
671
+ return;
672
+ }
673
+ const origin = this.skillNotificationOrigins.get(sessionId);
674
+ if (!origin)
675
+ return; // Diary only when the original destination is unavailable.
676
+ this.historyDb.insertMessage({ chatId: `${origin.source}-${origin.chatId}`, sessionId, source: origin.source, role: 'assistant', content: text, ts: Date.now() });
677
+ const index = await this.sessionStore.loadIndex(this.agentConfig.id, origin.chatId, origin.source);
678
+ if (index?.activeSessionId !== sessionId)
679
+ return;
680
+ const result = await (0, delivery_1.channelSender)(() => this.agentConfig)({ channel: origin.source, chat_id: origin.chatId, thread_key: origin.thread }, text, (0, crypto_1.randomUUID)());
681
+ if (result.state !== 'delivered')
682
+ this.logger.warn('Skill notification delivery failed', { sessionId, code: result.code });
683
+ }
420
684
  setSkillLearning(manager) {
421
685
  this.skillLearning = manager;
422
686
  }
@@ -442,7 +706,7 @@ class AgentRunner extends events_1.EventEmitter {
442
706
  req.on('data', (chunk) => {
443
707
  raw += chunk;
444
708
  });
445
- req.on('end', () => {
709
+ req.on('end', async () => {
446
710
  if (url.pathname === '/command') {
447
711
  this.handleCommandRequest(raw, res);
448
712
  return;
@@ -460,26 +724,79 @@ class AgentRunner extends events_1.EventEmitter {
460
724
  // receiver (Bun) can try to reuse the stale socket → "socket connection
461
725
  // was closed unexpectedly". Closing per-request avoids the race entirely.
462
726
  res.setHeader('Connection', 'close');
463
- res.writeHead(200);
464
- res.end('ok');
465
727
  try {
466
728
  const params = JSON.parse(raw);
467
729
  const meta = params.meta ?? {};
468
730
  const chatId = meta['chat_id'] ?? '';
469
731
  const content = params.content ?? '';
470
- // Set channel early so all handlers (including session commands) have it
471
- const channelSource = (meta['source'] === 'discord'
472
- ? 'discord'
473
- : meta['source'] === 'line'
474
- ? 'line'
475
- : meta['source'] === 'slack'
476
- ? 'slack'
477
- : meta['source'] === 'whatsapp'
478
- ? 'whatsapp'
479
- : meta['source'] === 'whatsapp_cloud'
480
- ? 'whatsapp_cloud'
481
- : 'telegram');
732
+ // Set channel early so all handlers (including session commands) have it.
733
+ // Derived from the canonical CHAT_CHANNELS list (see history/types.ts's
734
+ // doc comment) — a hand-copied ternary here is exactly what silently
735
+ // turned every WeChat (and, historically, Slack) message into
736
+ // 'telegram' before, since a new channel added elsewhere is easy to
737
+ // forget adding to a chain of `=== 'x' ? 'x' : ...` checks.
738
+ const channelSource = ((0, types_1.isChatChannel)(meta['source']) ? meta['source'] : 'telegram');
482
739
  this.channelSourceMap.set(chatId, channelSource);
740
+ const channelOrchestration = this.orchestration?.ownsChannel(channelSource, chatId) || (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource));
741
+ if (channelSource !== 'telegram' && this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource) && /^\/sessions?(?:\s|$)/.test(content.trim())) {
742
+ const index = await this.sessionStore.listSessions(this.agentConfig.id, chatId, channelSource);
743
+ const current = index.sessions.find(session => session.id === index.activeSessionId);
744
+ const text = content.trim() === '/sessions'
745
+ ? `Sessions\n${index.sessions.slice(0, 15).map(session => `${session.id === index.activeSessionId ? '✅ ' : ''}${session.name}\n${session.id}`).join('\n\n')}`
746
+ : `Current session: ${current?.name ?? '(unnamed)'}\n${index.activeSessionId}\nMode: Orchestration\nModel: ${this.agentConfig.claude.model}\nMessages: ${current?.messageCount ?? 0}\n\nCommands: /session /sessions /voice /voices /tasks /stop`;
747
+ await this.sendOrchestrationControl(channelSource, chatId, { text, buttons: [] }, meta);
748
+ res.writeHead(200);
749
+ res.end('ok');
750
+ return;
751
+ }
752
+ if (channelSource !== 'telegram' && (/^\/(voice|voices|tasks|stop|orch)(?:\s|$)/.test(content.trim()) || (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource) && content.trim() === '/help'))) {
753
+ const enabled = !!(this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channelSource));
754
+ if (enabled) {
755
+ const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource);
756
+ const runtime = await this.getOrchestration();
757
+ const scope = { channel: channelSource, chatId, thread: meta.thread_ts ?? meta.message_thread_id ?? '', sessionId, principalId: `${channelSource}:${meta.user_id ?? meta.user ?? chatId}` };
758
+ let menu;
759
+ try {
760
+ menu = await runtime.channelControls.handle(scope, content.trim());
761
+ }
762
+ catch {
763
+ menu = { text: 'Control unavailable or expired. Please open the command again.', buttons: [] };
764
+ }
765
+ await this.sendOrchestrationControl(channelSource, chatId, menu, meta);
766
+ res.writeHead(200);
767
+ res.end('ok');
768
+ return;
769
+ }
770
+ // Keep the legacy /stop path, but never turn disabled controls into model prompts.
771
+ if (!/^\/stop(?:\s|$)/.test(content.trim())) {
772
+ await this.sendOrchestrationControl(channelSource, chatId, { text: 'These controls require orchestration mode.', buttons: [] }, meta);
773
+ res.writeHead(200);
774
+ res.end('ok');
775
+ return;
776
+ }
777
+ }
778
+ if (channelOrchestration && !(0, builtin_commands_1.isBuiltinCommand)(content.trim(), channelSource)) {
779
+ const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource);
780
+ const orchestration = await this.getOrchestration();
781
+ const { media, quoted, details } = await (0, channel_input_media_1.channelInputMedia)(this.agentConfig, this.agentsBaseDir, channelSource, chatId, meta, AgentRunner.discardEphemeralStaging);
782
+ const senderId = meta.user_id ?? meta.user ?? chatId;
783
+ const accepted = orchestration.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId, source: channelSource,
784
+ accountId: this.agentConfig.id, chatId, threadKey: channelSource === 'whatsapp' && meta.account_id ? `whatsapp-account:${meta.account_id}` : meta.thread_ts ?? meta.message_thread_id ?? '', principalId: `${channelSource}:${senderId}` },
785
+ text: content || '[Attachment inspection requested]', attachmentIds: media, trustedChannelMember: true,
786
+ modality: ['voice', 'audio'].includes(meta.media_type ?? meta.attachment_kind) ? 'voice_note' : 'text',
787
+ ingressKey: meta.message_id, metadata: { senderId, senderName: meta.sender_name, platformMessageId: meta.message_id, platformMessageIds: meta.message_ids_json ? JSON.parse(meta.message_ids_json) : undefined, mediaGroupId: meta.media_group_id,
788
+ attachmentName: meta.attachment_name, mediaType: meta.media_type ?? meta.attachment_kind, repliedText: meta.replied_text, repliedMessageId: meta.replied_message_id, repliedSender: meta.replied_user, repliedAttachmentIds: quoted, attachmentDetails: details, attachmentError: meta.attachment_error } }, { execute: this.agentConfig.allow_tools !== false, writeMemory: this.agentConfig.allow_tools !== false });
789
+ // The durable receipt precedes receiver acknowledgment and inference.
790
+ res.writeHead(200);
791
+ res.end('ok');
792
+ void accepted.response.catch(error => {
793
+ // The durable decision/outbox owns failure delivery and activity, including autonomous turns.
794
+ this.logger.error('Conversation channel turn failed', { chatId, code: error.code ?? 'ORCHESTRATION_ERROR', ...(error.timeout ? { timeout: error.timeout } : {}) });
795
+ });
796
+ return;
797
+ }
798
+ res.writeHead(200);
799
+ res.end('ok');
483
800
  // Slack: remember the current message's thread context so the
484
801
  // auto-forward fallback (writeAutoForward) can stay in-thread. A
485
802
  // top-level message clears it so a later fallback isn't misrouted
@@ -548,6 +865,10 @@ class AgentRunner extends events_1.EventEmitter {
548
865
  return;
549
866
  }
550
867
  catch (err) {
868
+ if (!res.headersSent) {
869
+ res.writeHead(503);
870
+ res.end('Channel input was not accepted');
871
+ }
551
872
  this.logger.warn('Failed to parse channel callback body', {
552
873
  error: err.message,
553
874
  });
@@ -591,6 +912,10 @@ class AgentRunner extends events_1.EventEmitter {
591
912
  return;
592
913
  }
593
914
  const session = this.sessions.get(chatId);
915
+ if (session?.runtimeProfile || this.requiresDurableChannelIngress('telegram')) {
916
+ respond({ ok: false, error: 'Recovery is owned by orchestration; legacy turn replay is disabled.' });
917
+ return;
918
+ }
594
919
  const autoRecover = this.gatewayConfig.gateway.selfHealing?.autoRecover === true;
595
920
  const req = {
596
921
  incidentId: body.incidentId ?? '',
@@ -758,6 +1083,119 @@ class AgentRunner extends events_1.EventEmitter {
758
1083
  return;
759
1084
  }
760
1085
  const command = body.command;
1086
+ if (command === 'telegram_voices') {
1087
+ const chatId = body.chat_id ?? '', payload = body.payload ?? {};
1088
+ if (!/^\d+$/.test(chatId) || payload.user_id !== chatId) {
1089
+ respond({ success: false }, 400);
1090
+ return;
1091
+ }
1092
+ if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram')) {
1093
+ respond({ success: false, error: 'Voice controls unavailable.' });
1094
+ return;
1095
+ }
1096
+ try {
1097
+ const voices = (await this.getOrchestration()).telegramVoices;
1098
+ if (payload.action === 'dismiss' && typeof payload.menu_id === 'string') {
1099
+ voices.dismiss(chatId, payload.menu_id);
1100
+ respond({ success: true });
1101
+ }
1102
+ else if (payload.action === 'choose' && typeof payload.menu_id === 'string' && typeof payload.index === 'number')
1103
+ respond({ success: true, ...await voices.choose(chatId, payload.menu_id, payload.index) });
1104
+ else
1105
+ respond({ success: true, ...await voices.menu(chatId, typeof payload.page === 'number' ? payload.page : 0, typeof payload.menu_id === 'string' ? payload.menu_id : undefined, typeof payload.gender === 'string' ? payload.gender : undefined) });
1106
+ }
1107
+ catch {
1108
+ respond({ success: false, error: 'Voice list unavailable or provider changed. Use /voices to refresh.' });
1109
+ }
1110
+ return;
1111
+ }
1112
+ if (command === 'telegram_tasks') {
1113
+ const chatId = body.chat_id ?? '', payload = body.payload ?? {}, principalId = `telegram:${payload.user_id ?? ''}`;
1114
+ if (!/^\d+$/.test(chatId) || payload.user_id !== chatId) {
1115
+ respond({ success: false }, 400);
1116
+ return;
1117
+ }
1118
+ if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram')) {
1119
+ respond({ success: false, error: 'Task controls require orchestration mode.' });
1120
+ return;
1121
+ }
1122
+ try {
1123
+ const runtime = await this.getOrchestration();
1124
+ const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, 'telegram');
1125
+ if (payload.session_id !== undefined && payload.session_id !== sessionId) {
1126
+ respond({ success: false, error: 'TASK_SESSION_CHANGED' });
1127
+ return;
1128
+ }
1129
+ if (payload.action === 'detail' || payload.action === 'cancel') {
1130
+ if (typeof payload.task_id !== 'string')
1131
+ throw Error('INVALID_TASK');
1132
+ const task = payload.action === 'cancel' ? runtime.taskControls.cancel(sessionId, principalId, payload.task_id) : runtime.taskControls.detail(sessionId, principalId, payload.task_id);
1133
+ respond({ success: true, sessionId, task });
1134
+ }
1135
+ else
1136
+ respond({ success: true, sessionId, ...runtime.taskControls.list(sessionId, principalId, typeof payload.page === 'number' ? payload.page : 0) });
1137
+ }
1138
+ catch {
1139
+ respond({ success: false, error: 'Task unavailable. Use /tasks to refresh.' });
1140
+ }
1141
+ return;
1142
+ }
1143
+ if (command === 'task_stop') {
1144
+ const chatId = body.chat_id ?? '', principalId = `telegram:${body.payload?.user_id ?? ''}`;
1145
+ if (!/^\d+$/.test(chatId) || body.payload?.user_id !== chatId) {
1146
+ respond({ success: false, error: 'invalid_request' }, 400);
1147
+ return;
1148
+ }
1149
+ try {
1150
+ if (!(this.agentConfig.orchestration?.enabled && this.agentConfig.orchestration.channels?.includes('telegram'))) {
1151
+ if (body.payload?.menu_id) {
1152
+ respond({ success: false, error: 'Orchestration is disabled.' });
1153
+ return;
1154
+ }
1155
+ await this.handleCommandStop(chatId);
1156
+ respond({ success: true, legacy: true });
1157
+ return;
1158
+ }
1159
+ const runtime = await this.getOrchestration();
1160
+ const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, 'telegram');
1161
+ if (typeof body.payload?.menu_id === 'string' && typeof body.payload?.index === 'number') {
1162
+ respond({ success: true, ...runtime.stopControls.choose(sessionId, principalId, body.payload.menu_id, body.payload.index) });
1163
+ }
1164
+ else {
1165
+ const menu = runtime.stopControls.open(sessionId, principalId);
1166
+ respond({ success: true, ...menu, text: (0, stop_controls_1.stopMenuText)(menu, true) });
1167
+ }
1168
+ }
1169
+ catch {
1170
+ respond({ success: false, error: 'Selection unavailable. Use /stop to refresh the task list.' });
1171
+ }
1172
+ return;
1173
+ }
1174
+ if (command === 'telegram_voice') {
1175
+ const chatId = body.chat_id ?? '';
1176
+ const value = body.payload?.enabled;
1177
+ const mode = body.payload?.mode;
1178
+ if (!/^\d+$/.test(chatId) || (value !== undefined && typeof value !== 'boolean') || (mode !== undefined && !voice_reply_policy_1.VOICE_REPLY_MODES.includes(mode))) {
1179
+ respond({ success: false, error: 'invalid_request' }, 400);
1180
+ return;
1181
+ }
1182
+ if (!this.agentConfig.orchestration?.enabled || !this.agentConfig.orchestration.channels?.includes('telegram') || !this.agentConfig.voice?.enabled || this.agentConfig.voice?.notes?.replyWithVoice === false) {
1183
+ respond({ success: false, error: 'voice_not_configured' });
1184
+ return;
1185
+ }
1186
+ try {
1187
+ const runtime = await this.getOrchestration();
1188
+ if (mode !== undefined)
1189
+ runtime.store.setTelegramVoiceMode(chatId, mode);
1190
+ else if (typeof value === 'boolean')
1191
+ runtime.store.setTelegramVoice(chatId, value);
1192
+ respond({ success: true, enabled: runtime.store.telegramVoice(chatId), mode: runtime.store.telegramVoiceMode(chatId) });
1193
+ }
1194
+ catch {
1195
+ respond({ success: false, error: 'voice_settings_unavailable' }, 500);
1196
+ }
1197
+ return;
1198
+ }
761
1199
  if (command === 'get_model') {
762
1200
  respond({ model: this.agentConfig.claude.model });
763
1201
  return;
@@ -790,6 +1228,10 @@ class AgentRunner extends events_1.EventEmitter {
790
1228
  // its initData. The browser-facing routes live on the gateway; here we only
791
1229
  // mint the pairing and hand back the phone-openable link.
792
1230
  if (command === 'cli_pair') {
1231
+ if (this.gatewayConfig.gateway.headless !== false || this.agentConfig.type === 'app-agent' || this.agentConfig.orchestration?.enabled) {
1232
+ respond({ success: false, error: 'interactive_mode_required' });
1233
+ return;
1234
+ }
793
1235
  const payload = body.payload ?? {};
794
1236
  const channel = payload['channel'];
795
1237
  const userId = typeof payload['user_id'] === 'string' ? payload['user_id'] : '';
@@ -1074,7 +1516,21 @@ class AgentRunner extends events_1.EventEmitter {
1074
1516
  for (const entry of entries) {
1075
1517
  const meta = entry.meta ?? {};
1076
1518
  const content = entry.content ?? '';
1077
- const userContent = content || (meta['attachment_file_id'] || meta['image_path'] ? '(photo)' : meta['document_path'] ? '(document)' : meta['sticker_path'] ? '(sticker)' : '');
1519
+ // `image_path` is used for ANY attachment, not just photos (see
1520
+ // handleWeChatInboundMessage's doc comment) — a non-image
1521
+ // extension (e.g. WeChat's PDF/file support) must not show as
1522
+ // "(photo)" in the transcript.
1523
+ const isImagePath = /\.(jpe?g|png|gif|webp|heic|bmp)$/i.test(meta['image_path'] ?? '');
1524
+ const attachmentPlaceholder = meta['attachment_file_id']
1525
+ ? '(photo)'
1526
+ : meta['image_path']
1527
+ ? (isImagePath ? '(photo)' : '(file)')
1528
+ : meta['document_path']
1529
+ ? '(document)'
1530
+ : meta['sticker_path']
1531
+ ? '(sticker)'
1532
+ : '';
1533
+ const userContent = content || attachmentPlaceholder;
1078
1534
  const userTs = Date.now();
1079
1535
  await this.sessionStore.appendTelegramMessage(this.agentConfig.id, chatId, sessionId, {
1080
1536
  role: 'user',
@@ -1220,6 +1676,8 @@ class AgentRunner extends events_1.EventEmitter {
1220
1676
  session.setProcessing(true);
1221
1677
  const turnText = blocks.join('\n');
1222
1678
  // Skill-learning: mark the start of a user turn (cheap, best-effort).
1679
+ const originMeta = entries[0]?.meta ?? {};
1680
+ this.skillNotificationOrigins.set(sessionId, { source: channelSource, chatId, thread: originMeta.thread_ts ?? originMeta.message_thread_id ?? '' });
1223
1681
  this.skillLearning?.onTurnStart(chatId, sessionId, entries[0]?.content ?? '', invokedSkills);
1224
1682
  session.sendMessage(turnText);
1225
1683
  // Remember this turn for the C1 guarded resend (Phase 3b): reset the
@@ -1717,6 +2175,9 @@ class AgentRunner extends events_1.EventEmitter {
1717
2175
  let lastAssistantTextThisTurn = '';
1718
2176
  let typingDoneTimer = null;
1719
2177
  const TYPING_DONE_DELAY_MS = 3000;
2178
+ // Same hand-copied-ternary gap CHAT_CHANNELS' doc comment warns about —
2179
+ // 'wechat' was missing here too (confirmed live 2026-09-11), so this
2180
+ // turn-completion tracking never recognized a wechat_reply tool call.
1720
2181
  const replyToolName = source === 'discord'
1721
2182
  ? 'mcp__gateway__discord_reply'
1722
2183
  : source === 'line'
@@ -1727,7 +2188,9 @@ class AgentRunner extends events_1.EventEmitter {
1727
2188
  ? 'mcp__gateway__whatsapp_reply'
1728
2189
  : source === 'whatsapp_cloud'
1729
2190
  ? 'mcp__gateway__whatsapp_cloud_reply'
1730
- : 'mcp__gateway__telegram_reply';
2191
+ : source === 'wechat'
2192
+ ? 'mcp__gateway__wechat_reply'
2193
+ : 'mcp__gateway__telegram_reply';
1731
2194
  proc.on('output', (line) => {
1732
2195
  try {
1733
2196
  const obj = JSON.parse(line);
@@ -2090,7 +2553,7 @@ class AgentRunner extends events_1.EventEmitter {
2090
2553
  // event. Start the typing-done timer here so the indicator stays alive
2091
2554
  // through multi-turn tool-call sequences and only stops when all work is done.
2092
2555
  if (obj['type'] === 'session_idle' && proc.backend === 'pty-shell') {
2093
- // A parent can become idle immediately after dispatching background
2556
+ // An agent can become idle immediately after dispatching background
2094
2557
  // Agent/Workflow work. Keep the Telegram typing signal and processing
2095
2558
  // sentinel alive until the task notification starts its follow-up turn.
2096
2559
  const waitingForBackgroundWork = proc.retainBackgroundWorkingState();
@@ -2146,7 +2609,7 @@ class AgentRunner extends events_1.EventEmitter {
2146
2609
  }, this.channelFor(mapKey)).catch(() => { });
2147
2610
  }
2148
2611
  proc.on('backgroundWorkExpired', () => {
2149
- // The child task never notified its parent within the bounded grace
2612
+ // The child task never notified its agent within the bounded grace
2150
2613
  // window; release the Telegram typing signal instead of leaving it live.
2151
2614
  this.writeTypingDone(mapKey);
2152
2615
  });
@@ -2211,6 +2674,9 @@ class AgentRunner extends events_1.EventEmitter {
2211
2674
  });
2212
2675
  return proc;
2213
2676
  }
2677
+ apiTaskStopReply(sessionId, principalId, text) {
2678
+ return this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api') ? this.orchestration?.stopControls.replyCommand(sessionId, principalId, text) : undefined;
2679
+ }
2214
2680
  static isApiBuiltinCommand(content) {
2215
2681
  return (0, builtin_commands_1.isBuiltinCommand)(content, 'api');
2216
2682
  }
@@ -2410,6 +2876,13 @@ class AgentRunner extends events_1.EventEmitter {
2410
2876
  * "No turn in progress." that fired whenever interrupt() no-op'd mid-spawn.
2411
2877
  */
2412
2878
  async handleCommandStop(chatId) {
2879
+ if (this.orchestration && this.agentConfig.orchestration?.enabled) {
2880
+ const sessionId = await this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, this.channelFor(chatId));
2881
+ if (this.orchestration.stopResponse(sessionId)) {
2882
+ this.writeAutoForward(chatId, 'Response stopped. Tasks remain active.');
2883
+ return;
2884
+ }
2885
+ }
2413
2886
  // Cancel queued + buffered work first so nothing flushes in behind the interrupt.
2414
2887
  const buffered = this.channelCoalesce.get(chatId);
2415
2888
  if (buffered?.timer)
@@ -2791,7 +3264,9 @@ class AgentRunner extends events_1.EventEmitter {
2791
3264
  const { immediate, deferred } = await this.restartOrDefer({
2792
3265
  skipBusy: false,
2793
3266
  deferIdle: true,
2794
- filter: (proc) => proc.connectorConfigChanged(connectorId, target),
3267
+ // Managed turns use a fresh subprocess/config on their next decision or task.
3268
+ // Never send them through legacy restart/replay while effects may be in flight.
3269
+ filter: (proc) => !proc.runtimeProfile && proc.connectorConfigChanged(connectorId, target),
2795
3270
  });
2796
3271
  return { restarted: immediate + deferred > 0 };
2797
3272
  }
@@ -2935,6 +3410,21 @@ class AgentRunner extends events_1.EventEmitter {
2935
3410
  }
2936
3411
  return;
2937
3412
  }
3413
+ // WeChat has no .forward consumer either — same gap that used to silently
3414
+ // drop every command reply / no-tool-call fallback for Slack (confirmed
3415
+ // live 2026-09-11: the agent's plain-text reply showed up in the web
3416
+ // session view but never reached the user's phone, because it landed in
3417
+ // a chatId.forward file nothing reads for this channel). Deliver via the
3418
+ // manager directly, mirroring Slack's branch above.
3419
+ if (this.channelFor(chatId) === 'wechat') {
3420
+ void this.wechat?.sendMessage(chatId, text).catch((err) => {
3421
+ this.logger.warn('WeChat auto-forward failed', {
3422
+ chatId,
3423
+ error: err instanceof Error ? err.message : String(err),
3424
+ });
3425
+ });
3426
+ return;
3427
+ }
2938
3428
  const typingDir = this.getTypingDir(chatId);
2939
3429
  const forwardPath = path.join(typingDir, `${chatId}.forward`);
2940
3430
  try {
@@ -3003,7 +3493,7 @@ class AgentRunner extends events_1.EventEmitter {
3003
3493
  startIdleCleaner() {
3004
3494
  this.idleCleanerTimer = setInterval(async () => {
3005
3495
  for (const [id, proc] of this.sessions) {
3006
- // A parent that just dispatched Agent/Workflow/Monitor work is CLI-idle
3496
+ // An agent that just dispatched Agent/Workflow/Monitor work is CLI-idle
3007
3497
  // but that dispatch may still need this session to receive its
3008
3498
  // eventual task notification.
3009
3499
  if (proc.hasLikelyOutstandingBackgroundWork())
@@ -3018,16 +3508,20 @@ class AgentRunner extends events_1.EventEmitter {
3018
3508
  }, 5 * 60 * 1000);
3019
3509
  }
3020
3510
  async start() {
3511
+ if (this.agentConfig.orchestration?.enabled || fs.existsSync(path.join(this.agentDir, 'orchestration.db')))
3512
+ await this.getOrchestration();
3021
3513
  this.stopping = false;
3022
3514
  await this.sweepStaleSessionDirs();
3023
3515
  await this.startCallbackServer();
3024
3516
  if (this.agentConfig.telegram?.botToken) {
3025
- this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
3517
+ this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
3026
3518
  this.receiver.start();
3519
+ this.telegramCommandConfig = this.telegramCommandsKey();
3027
3520
  }
3028
3521
  if (this.agentConfig.discord?.botToken) {
3029
- this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
3522
+ this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
3030
3523
  this.discordReceiver.start();
3524
+ this.discordCommandConfig = this.telegramCommandsKey();
3031
3525
  }
3032
3526
  // LINE slow-LLM postback button: gateway-side token lifecycle + cache.
3033
3527
  // Enabled for any LINE agent unless its threshold is 0 (then the MCP
@@ -3043,6 +3537,8 @@ class AgentRunner extends events_1.EventEmitter {
3043
3537
  // no-ops here and stays 'unlinked' until the user starts a QR/pairing
3044
3538
  // flow via the API.
3045
3539
  this.syncWhatsAppAccounts();
3540
+ this.ensureWeChatManager();
3541
+ await this.wechat?.resumeIfLinked();
3046
3542
  this.startIdleCleaner();
3047
3543
  this._startCleanupScheduler();
3048
3544
  this.logger.info('AgentRunner started', { agentId: this.agentConfig.id });
@@ -3086,8 +3582,34 @@ class AgentRunner extends events_1.EventEmitter {
3086
3582
  }
3087
3583
  }
3088
3584
  }
3585
+ telegramCommandsKey() {
3586
+ return JSON.stringify([this.gatewayConfig.gateway.headless !== false, this.agentConfig.type,
3587
+ Boolean(this.agentConfig.orchestration?.enabled), this.agentConfig.orchestration?.channels ?? ['api']]);
3588
+ }
3589
+ /** Reload only the receiver when command visibility changes; Agent/Worker work continues. */
3590
+ refreshTelegramCommands() {
3591
+ const discord = this.discordReceiver, discordKey = this.telegramCommandsKey();
3592
+ if (discord && discordKey !== this.discordCommandConfig) {
3593
+ this.discordCommandConfig = discordKey;
3594
+ void discord.stop().then(() => { if (this.discordReceiver !== discord || this.stopping)
3595
+ return; this.discordReceiver = null; this.startDiscordReceiver(); });
3596
+ }
3597
+ const previous = this.receiver, key = this.telegramCommandsKey();
3598
+ if (!previous || key === this.telegramCommandConfig)
3599
+ return;
3600
+ this.telegramCommandConfig = key;
3601
+ void previous.stop().then(() => {
3602
+ if (this.receiver !== previous || this.stopping)
3603
+ return;
3604
+ this.receiver = null;
3605
+ this.startTelegramReceiver();
3606
+ });
3607
+ }
3089
3608
  updateAgentConfig(newConfig) {
3609
+ newConfig = (0, gateway_config_1.applyGatewayOrchestration)(newConfig, this.gatewayConfig);
3610
+ this.orchestration?.updateAgentConfig(newConfig);
3090
3611
  this.agentConfig = newConfig;
3612
+ this.refreshTelegramCommands();
3091
3613
  // Restart LineReplyManager if the LINE config changed so the live instance
3092
3614
  // picks up a new access token, threshold, or labels without a full restart.
3093
3615
  this.stopLineReply();
@@ -3104,6 +3626,10 @@ class AgentRunner extends events_1.EventEmitter {
3104
3626
  // (access-control fields) into every already-running manager, and
3105
3627
  // add/remove managers only if the set of configured accounts changed.
3106
3628
  this.syncWhatsAppAccounts();
3629
+ // WeChat has no credential to react to (QR-linked, not config-driven) —
3630
+ // just hand the manager the fresh config so its access-control reads
3631
+ // (dmPolicy/dmAllowlist/pairing) see the latest values.
3632
+ this.wechat?.updateAgentConfig(newConfig);
3107
3633
  }
3108
3634
  /**
3109
3635
  * Reconcile `this.whatsappAccounts` with `agentConfig.whatsapp.accounts`.
@@ -3220,6 +3746,152 @@ class AgentRunner extends events_1.EventEmitter {
3220
3746
  opts) {
3221
3747
  await this.whatsAppManagerFor(jid, accountId).sendMessage(jid, text, imagePath, opts);
3222
3748
  }
3749
+ /** Lazily construct this agent's WeChatManager — safe to call repeatedly. */
3750
+ ensureWeChatManager() {
3751
+ if (!this.wechat) {
3752
+ // WECHAT_ILINK_FAKE is local-testing-only (see ilink-client.ts's
3753
+ // createFakeILinkClient doc comment) — never set in a real deployment.
3754
+ // The real client needs no account/credential of its own (confirmed
3755
+ // against Tencent's own protocol doc — see ilink-client.ts's module
3756
+ // comment), this flag exists purely to skip a real QR scan while
3757
+ // iterating on the UI.
3758
+ const client = (0, ilink_client_1.isWeChatILinkFakeEnabled)()
3759
+ ? (0, ilink_client_1.createFakeILinkClient)()
3760
+ : (0, ilink_client_1.createILinkClient)(process.env.ILINK_BASE_URL ?? '', this.agentConfig.wechat?.botAgent);
3761
+ this.wechat = new manager_2.WeChatManager(this.agentConfig, this.gatewayConfig.gateway.logDir, client, (update) => this.handleWeChatInboundMessage(update));
3762
+ }
3763
+ return this.wechat;
3764
+ }
3765
+ getWeChatStatus() {
3766
+ return this.ensureWeChatManager().getStatus();
3767
+ }
3768
+ async startWeChatLinking() {
3769
+ await this.ensureWeChatManager().startLinking();
3770
+ }
3771
+ async unlinkWeChat() {
3772
+ await this.ensureWeChatManager().unlink();
3773
+ }
3774
+ async sendWeChatMessage(toId, text) {
3775
+ await this.ensureWeChatManager().sendMessage(toId, text);
3776
+ }
3777
+ /**
3778
+ * Gate + forward one inbound WeChat message. Mirrors the LINE webhook
3779
+ * router's own gate-then-forward-to-/channel shape exactly (see
3780
+ * src/api/line-webhook-router.ts's `recordDeniedSender` call and its
3781
+ * "forward to the agent's existing /channel intake" comment) — WeChat has
3782
+ * no webhook of its own, so this is the equivalent point for a message
3783
+ * arriving off WeChatManager's long-poll loop instead of an Express route.
3784
+ */
3785
+ handleWeChatInboundMessage(update) {
3786
+ const cfg = this.agentConfig.wechat;
3787
+ const allowed = (0, wechat_access_1.isWeChatConversationAllowed)(cfg, { fromId: update.fromId });
3788
+ if (!allowed) {
3789
+ const pairingOn = cfg?.pairing !== false; // absent ⇒ on, mirrors line.pairing/slack.pairing
3790
+ const existing = (0, pending_senders_1.getPendingSender)('wechat', this.agentConfig.id, update.fromId);
3791
+ const code = pairingOn ? (existing?.code ?? (0, pending_senders_1.generatePairingCode)()) : undefined;
3792
+ const isNew = (0, pending_senders_1.recordDeniedSender)('wechat', this.agentConfig.id, update.fromId, update.displayName, Date.now(), code);
3793
+ if (isNew && pairingOn && code) {
3794
+ this.wechat
3795
+ ?.sendMessage(update.fromId, `This account isn't approved to message this agent yet. Give the admin this code: ${code}`)
3796
+ .catch((err) => this.logger.warn('WeChat pairing-code reply failed', { error: err.message }));
3797
+ }
3798
+ return;
3799
+ }
3800
+ // Self-heal a stale pending-knock record for a sender who's now allowed.
3801
+ // Approving a sender already clears their pending entry (router.ts's
3802
+ // wechat_dm_allowlist PATCH handler), but confirmed live 2026-09-11: a
3803
+ // sender can still show as both allowed AND pending in the UI, most
3804
+ // likely a race between that approval and a message processed in the
3805
+ // same window. Clearing it here too means any such staleness can't
3806
+ // outlive this sender's very next allowed message.
3807
+ (0, pending_senders_1.clearPendingSender)('wechat', this.agentConfig.id, update.fromId);
3808
+ const meta = {
3809
+ source: 'wechat',
3810
+ chat_id: update.fromId,
3811
+ user_id: update.fromId,
3812
+ user: update.displayName || update.fromId,
3813
+ message_id: update.id,
3814
+ ts: new Date(update.timestamp ?? Date.now()).toISOString(),
3815
+ };
3816
+ void (async () => {
3817
+ // Stage an inbound image or file to a temp path the same way LINE's
3818
+ // webhook router does (src/api/line-webhook-router.ts's
3819
+ // downloadLineImage/drainToFile pattern) — AgentRunner.injectTurn
3820
+ // already knows how to take meta.image_path from here (the field name
3821
+ // is historical; the mechanism itself is content-agnostic — it just
3822
+ // copies whatever's at that path into MediaStore), rewrite the path
3823
+ // for the agent's sandbox, and clean up the ephemeral original. No
3824
+ // format sniffing for images: always staged as .jpg, matching
3825
+ // Hermes-agent's own assumption for this same protocol. Files keep
3826
+ // their real name/extension (sniffing isn't needed — it's given).
3827
+ // update.id (Tencent's message_id) arrives as a JSON number at runtime
3828
+ // despite ILinkUpdate declaring it a string (confirmed live
3829
+ // 2026-09-11: `.replace is not a function` on a real image message) —
3830
+ // String() it before sanitizing.
3831
+ const safeId = String(update.id).replace(/[^a-zA-Z0-9_-]/g, '_');
3832
+ if (update.image) {
3833
+ try {
3834
+ const bytes = await (0, ilink_client_1.downloadWeixinImage)(update.image, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
3835
+ const tempPath = path.join(os.tmpdir(), `wechat-img-${safeId}-${Date.now()}.jpg`);
3836
+ await fsPromises.writeFile(tempPath, bytes);
3837
+ meta.image_path = tempPath;
3838
+ meta.media_ephemeral = '1';
3839
+ }
3840
+ catch (err) {
3841
+ this.logger.warn('WeChat image download failed, forwarding text only', {
3842
+ error: err.message,
3843
+ });
3844
+ }
3845
+ }
3846
+ else if (update.file) {
3847
+ try {
3848
+ const bytes = await (0, ilink_client_1.downloadWeixinImage)(update.file, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
3849
+ // Preserve the sender's real filename/extension (unlike images,
3850
+ // which are always staged as .jpg) — the agent's Read tool needs a
3851
+ // correct extension to make sense of a PDF/docx/etc.
3852
+ const safeName = update.file.fileName.replace(/[^a-zA-Z0-9_.-]/g, '_') || 'file';
3853
+ const tempPath = path.join(os.tmpdir(), `wechat-file-${safeId}-${Date.now()}-${safeName}`);
3854
+ await fsPromises.writeFile(tempPath, bytes);
3855
+ meta.image_path = tempPath;
3856
+ meta.media_ephemeral = '1';
3857
+ }
3858
+ catch (err) {
3859
+ this.logger.warn('WeChat file download failed, forwarding text only', {
3860
+ error: err.message,
3861
+ });
3862
+ }
3863
+ }
3864
+ if (update.reply) {
3865
+ if (update.reply.messageId)
3866
+ meta.replied_message_id = update.reply.messageId;
3867
+ if (update.reply.text)
3868
+ meta.replied_text = update.reply.text;
3869
+ const ref = update.reply.image ?? update.reply.file;
3870
+ if (ref)
3871
+ try {
3872
+ const bytes = await (0, ilink_client_1.downloadWeixinImage)(ref, { maxBytes: media_store_1.MediaStore.maxUploadBytes });
3873
+ const name = update.reply.file?.fileName.replace(/[^a-zA-Z0-9_.-]/g, '_') ?? 'image.jpg';
3874
+ const dest = path.join(os.tmpdir(), `wechat-reply-${safeId}-${Date.now()}-${name}`);
3875
+ await fsPromises.writeFile(dest, bytes, { mode: 0o600 });
3876
+ meta.replied_image_path = dest;
3877
+ meta.media_ephemeral = '1';
3878
+ }
3879
+ catch {
3880
+ meta.attachment_error = 'The quoted attachment could not be downloaded. Do not claim to have read it.';
3881
+ }
3882
+ }
3883
+ try {
3884
+ await fetch(`http://127.0.0.1:${this.callbackPort}/channel`, {
3885
+ method: 'POST',
3886
+ headers: { 'Content-Type': 'application/json' },
3887
+ body: JSON.stringify({ content: update.text ?? '', meta }),
3888
+ });
3889
+ }
3890
+ catch (err) {
3891
+ this.logger.error('WeChat: failed to forward to callback', { error: err.message });
3892
+ }
3893
+ })();
3894
+ }
3223
3895
  startSlackOutbound() {
3224
3896
  if (!this.agentConfig.slack?.botToken || !this.agentConfig.slack?.signingSecret)
3225
3897
  return;
@@ -3285,8 +3957,9 @@ class AgentRunner extends events_1.EventEmitter {
3285
3957
  return;
3286
3958
  if (!this.agentConfig.telegram?.botToken)
3287
3959
  return;
3288
- this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
3960
+ this.receiver = new receiver_1.TelegramReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
3289
3961
  this.receiver.start();
3962
+ this.telegramCommandConfig = this.telegramCommandsKey();
3290
3963
  this.logger.info('TelegramReceiver hot-started', { agentId: this.agentConfig.id });
3291
3964
  }
3292
3965
  stopTelegramReceiver() {
@@ -3304,8 +3977,9 @@ class AgentRunner extends events_1.EventEmitter {
3304
3977
  return;
3305
3978
  if (!this.agentConfig.discord?.botToken)
3306
3979
  return;
3307
- this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
3980
+ this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir, this.gatewayConfig.gateway.headless !== false);
3308
3981
  this.discordReceiver.start();
3982
+ this.discordCommandConfig = this.telegramCommandsKey();
3309
3983
  this.logger.info('DiscordReceiver hot-started', { agentId: this.agentConfig.id });
3310
3984
  }
3311
3985
  stopDiscordReceiver() {
@@ -3320,6 +3994,12 @@ class AgentRunner extends events_1.EventEmitter {
3320
3994
  }
3321
3995
  async stop() {
3322
3996
  this.stopping = true;
3997
+ if (this.orchestrationStarting) {
3998
+ const orchestration = await this.orchestrationStarting.catch(() => undefined);
3999
+ await orchestration?.close();
4000
+ this.orchestration = undefined;
4001
+ this.orchestrationStarting = undefined;
4002
+ }
3323
4003
  if (this.idleCleanerTimer !== null) {
3324
4004
  clearInterval(this.idleCleanerTimer);
3325
4005
  this.idleCleanerTimer = null;
@@ -3402,6 +4082,17 @@ class AgentRunner extends events_1.EventEmitter {
3402
4082
  isRunning() {
3403
4083
  return this.receiver?.isRunning() ?? false;
3404
4084
  }
4085
+ getOrchestrationSummary() {
4086
+ if (!this.agentConfig.orchestration?.enabled && !this.orchestration)
4087
+ return undefined;
4088
+ const summary = this.orchestration?.dashboardSummary() ?? { enabled: true, backend: 'headless', workspaceMode: this.agentConfig.type === 'app-agent' ? 'container' : this.agentConfig.orchestration?.tasks?.workspaceMode ?? 'host', activeAgentSessions: [], tasks: [], sessions: [] };
4089
+ const processes = [...this.sessions.values()].filter(p => p.runtimeProfile?.role === 'agent' && p.isRunning());
4090
+ return { ...summary, sessions: summary.sessions.map(s => {
4091
+ const p = processes.find(p => p.sessionId === s.sessionId);
4092
+ return { ...s, model: p?.model ?? s.model, tokens: p?.totalTokens ?? 0, spawnedAt: p?.spawnedAt ?? 0,
4093
+ uptimeSec: p ? Math.floor((Date.now() - p.spawnedAt) / 1000) : 0 };
4094
+ }), agentProcesses: processes.map(p => ({ sessionId: p.sessionId, hostProcessId: p.processId, container: this.agentConfig.container })) };
4095
+ }
3405
4096
  getSessionsSummary() {
3406
4097
  const now = Date.now();
3407
4098
  // A single logical session can appear multiple times in the ring buffer: a
@@ -3439,6 +4130,8 @@ class AgentRunner extends events_1.EventEmitter {
3439
4130
  * - Appends user message and assistant reply to SessionStore for history persistence.
3440
4131
  */
3441
4132
  async sendApiMessage(sessionId, chatId, message, opts) {
4133
+ if (this.orchestrationForApi(sessionId))
4134
+ return this.sendOrchestratedApi(sessionId, chatId, message, opts);
3442
4135
  if (this.pendingApiSessions.has(sessionId)) {
3443
4136
  const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
3444
4137
  throw err;
@@ -3721,6 +4414,18 @@ class AgentRunner extends events_1.EventEmitter {
3721
4414
  * expires (#421).
3722
4415
  */
3723
4416
  async sendApiMessageStream(sessionId, chatId, message, callbacks, opts) {
4417
+ if (this.orchestrationForApi(sessionId)) {
4418
+ if (this.pendingApiSessions.has(sessionId))
4419
+ throw Object.assign(new Error('Session already has a pending request'), { code: 'CONFLICT' });
4420
+ const turn = this.turnStreams.start((0, turn_stream_1.turnStreamKey)('api', sessionId), opts.requestId ?? (0, crypto_1.randomUUID)());
4421
+ const sink = (0, turn_stream_1.callbackSink)(callbacks);
4422
+ turn.attach(sink, 0);
4423
+ void this.sendOrchestratedApi(sessionId, chatId, message, { ...opts, requestId: turn.requestId }, text => turn.emit({ type: 'text_delta', text }), event => { if (event.type === 'tool_use')
4424
+ turn.emit({ ...event, type: 'tool_use' }); })
4425
+ .then(result => this.turnStreams.complete(turn, (0, turn_stream_1.resultEvent)(result.text, result.attachments)))
4426
+ .catch(error => this.turnStreams.complete(turn, (0, turn_stream_1.errorEvent)(error), error));
4427
+ return () => turn.detach(sink);
4428
+ }
3724
4429
  if (this.pendingApiSessions.has(sessionId)) {
3725
4430
  const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
3726
4431
  throw err;
@@ -4192,6 +4897,8 @@ class AgentRunner extends events_1.EventEmitter {
4192
4897
  if (!AgentRunner.isApiBuiltinCommand(cmd)) {
4193
4898
  throw new Error(`Unknown command: ${cmd}`);
4194
4899
  }
4900
+ if (cmd === '/stop' && this.agentConfig.orchestration?.enabled && opts?.principalId)
4901
+ this.orchestration?.authorizeSession(sessionId, opts.principalId);
4195
4902
  // Register session in the api-{chatId} index on first use (same as sendApiMessageStream)
4196
4903
  await this.sessionStore.ensureApiSession(agentId, storeChatId, sessionId).catch((err) => {
4197
4904
  this.logger.warn('Failed to register API session in index for command', { agentId, chatId, sessionId, error: err.message });
@@ -4214,7 +4921,7 @@ class AgentRunner extends events_1.EventEmitter {
4214
4921
  // Persist the full user command before executing so it appears in history.
4215
4922
  // Skip for /clear — clearSession() below wipes the table anyway; only the response survives.
4216
4923
  if (cmd !== '/clear') {
4217
- persist('user', command);
4924
+ persist('user', opts?.displayCommand ?? command);
4218
4925
  }
4219
4926
  let result;
4220
4927
  let responseText;
@@ -4229,17 +4936,41 @@ class AgentRunner extends events_1.EventEmitter {
4229
4936
  : `Current model: ${model}`;
4230
4937
  }
4231
4938
  else if (cmd === '/stop') {
4232
- const session = this.sessions.get(sessionId);
4233
- const stopped = session ? session.interrupt() : false;
4234
- result = { stopped };
4235
- responseText = stopped
4236
- ? 'Session was interrupted before I could respond.'
4237
- : 'No active session to stop.';
4238
- // A turn was actually cut off — always leave a visible note in history so the
4239
- // web Stop button (skipPersist:true, to suppress the "/stop" command echo) and
4240
- // a typed /stop command show the same outcome instead of the button leaving a
4241
- // silently-dangling user turn.
4242
- forcePersist = stopped;
4939
+ if (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes('api') && this.orchestration && opts?.principalId) {
4940
+ const selection = /^\/stop\s+([a-f0-9-]{36})\s+(\d+)$/.exec(command.trim());
4941
+ if (selection) {
4942
+ try {
4943
+ const selected = this.orchestration.stopControls.choose(sessionId, opts.principalId, selection[1], Number(selection[2]));
4944
+ result = { ...selected };
4945
+ responseText = selected.text;
4946
+ }
4947
+ catch {
4948
+ result = { error: 'STOP_SELECTION_UNAVAILABLE' };
4949
+ responseText = 'Selection unavailable. Use /stop to refresh the task list.';
4950
+ }
4951
+ }
4952
+ else {
4953
+ const menu = this.orchestration.stopControls.open(sessionId, opts.principalId);
4954
+ responseText = (0, stop_controls_1.stopMenuText)(menu);
4955
+ result = { ...menu, responseText };
4956
+ }
4957
+ forcePersist = true;
4958
+ }
4959
+ else {
4960
+ const session = this.sessions.get(sessionId);
4961
+ const stopped = this.agentConfig.orchestration?.enabled && this.orchestration?.isBusy(sessionId)
4962
+ ? this.orchestration.stopResponse(sessionId)
4963
+ : session ? session.interrupt() : false;
4964
+ result = { stopped };
4965
+ responseText = stopped
4966
+ ? 'Session was interrupted before I could respond.'
4967
+ : 'No active session to stop.';
4968
+ // A turn was actually cut off — always leave a visible note in history so the
4969
+ // web Stop button (skipPersist:true, to suppress the "/stop" command echo) and
4970
+ // a typed /stop command show the same outcome instead of the button leaving a
4971
+ // silently-dangling user turn.
4972
+ forcePersist = stopped;
4973
+ }
4243
4974
  }
4244
4975
  else if (cmd === '/restart') {
4245
4976
  this.restartProcess(sessionId).catch(() => { });
@@ -4519,6 +5250,100 @@ class AgentRunner extends events_1.EventEmitter {
4519
5250
  const mediaPaths = this.historyDb.clearSession(`api-${chatId}`, sessionId);
4520
5251
  media_store_1.MediaStore.deleteMediaFiles(this.agentsBaseDir, this.agentConfig.id, mediaPaths);
4521
5252
  }
5253
+ /** The route has already authorized this API principal for the Agent. Preserve
5254
+ * the original channel binding, but never impersonate its human sender. */
5255
+ async sendOrchestratedChannel(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
5256
+ if (!opts.principalId)
5257
+ throw new Error('Authenticated principal required for conversation orchestration');
5258
+ const runtime = await this.getOrchestration();
5259
+ const rows = runtime.store.all('SELECT * FROM conversations WHERE agent_session_id=?', sessionId);
5260
+ if (rows.length > 1 || rows.some(row => row.source !== channel || row.chat_id !== rawChatId))
5261
+ throw new Error('Ambiguous or mismatched channel session');
5262
+ if (!rows.length) {
5263
+ const index = await this.sessionStore.loadIndex(this.agentConfig.id, rawChatId, channel);
5264
+ if (!index?.sessions.some(session => session.id === sessionId))
5265
+ throw new Error('Session not found');
5266
+ }
5267
+ const requestId = opts.requestId ?? (0, crypto_1.randomUUID)();
5268
+ const accepted = runtime.submitInput({ scope: { agentId: this.agentConfig.id, agentSessionId: sessionId,
5269
+ source: channel, accountId: String(rows[0]?.account_id ?? this.agentConfig.id), chatId: rawChatId,
5270
+ threadKey: String(rows[0]?.thread_key ?? ''), principalId: opts.principalId },
5271
+ text: message, requestId, trustedChannelMember: true, metadata: { senderName, senderId: opts.principalId } }, { execute: opts.allowTools ?? false, writeMemory: false });
5272
+ const turn = this.turnStreams.start((0, turn_stream_1.turnStreamKey)(channel, sessionId), requestId);
5273
+ const sink = (0, turn_stream_1.callbackSink)(callbacks);
5274
+ turn.attach(sink, 0);
5275
+ const displayed = new Map();
5276
+ const unsubscribe = runtime.subscribeText(sessionId, opts.principalId, event => {
5277
+ if (!runtime.responseBelongsToInput(accepted.inputId, event.responseId))
5278
+ return;
5279
+ const previous = displayed.get(event.responseId) ?? '';
5280
+ if (event.text.startsWith(previous)) {
5281
+ const delta = event.text.slice(previous.length);
5282
+ displayed.set(event.responseId, event.text);
5283
+ if (delta)
5284
+ turn.emit({ type: 'text_delta', text: delta });
5285
+ }
5286
+ });
5287
+ const input = runtime.store.get('SELECT conversation_id FROM conversation_inputs WHERE id=?', accepted.inputId);
5288
+ const conversation = runtime.store.assertMember(String(input.conversation_id), opts.principalId);
5289
+ let cursor = { streamId: String(conversation.stream_id), seq: Number(conversation.last_event_seq) };
5290
+ const events = runtime.events.subscribe(String(conversation.id), opts.principalId, cursor);
5291
+ let closed = false;
5292
+ let timer;
5293
+ const close = () => { closed = true; clearTimeout(timer); unsubscribe(); events.close(); };
5294
+ const forward = (page) => {
5295
+ if (page.snapshotRequired)
5296
+ throw new Error('Tool activity history expired; reload session activity');
5297
+ for (const event of page.events) {
5298
+ if (event.seq <= cursor.seq)
5299
+ continue;
5300
+ const payload = event.payload;
5301
+ if (event.type === 'tool.activity' && payload.type === 'tool_use')
5302
+ turn.emit({ ...payload, type: 'tool_use' });
5303
+ cursor = { streamId: page.cursor.streamId, seq: event.seq };
5304
+ }
5305
+ };
5306
+ const drain = () => {
5307
+ while (true) {
5308
+ const page = runtime.events.read(String(conversation.id), opts.principalId, cursor);
5309
+ forward(page);
5310
+ if (!page.snapshotRequired && !page.events.length)
5311
+ break;
5312
+ }
5313
+ };
5314
+ const fail = (error) => {
5315
+ if (closed)
5316
+ return;
5317
+ try {
5318
+ drain();
5319
+ }
5320
+ catch { /* Preserve the original stream/provider failure. */ }
5321
+ this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.errorEvent)(error), error);
5322
+ close();
5323
+ };
5324
+ void (async () => {
5325
+ for await (const page of events.pages) {
5326
+ if (closed)
5327
+ break;
5328
+ forward(page);
5329
+ }
5330
+ })().catch(fail);
5331
+ timer = setTimeout(() => fail(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT_SOFT' })), opts.timeoutMs);
5332
+ void accepted.response.then(text => {
5333
+ if (closed)
5334
+ return;
5335
+ try {
5336
+ // Drain before terminal success, even when completion precedes the next poll.
5337
+ drain();
5338
+ this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.resultEvent)(text, []));
5339
+ close();
5340
+ }
5341
+ catch (error) {
5342
+ fail(error instanceof Error ? error : new Error('Tool activity stream failed'));
5343
+ }
5344
+ }, fail);
5345
+ return () => { turn.detach(sink); }; // Durable work and original channel delivery survive disconnect.
5346
+ }
4522
5347
  /**
4523
5348
  * Send a message into an existing channel session (cross-channel continuation from UI).
4524
5349
  * The session process receives full history context from the session JSON (Layer 1).
@@ -4528,6 +5353,10 @@ class AgentRunner extends events_1.EventEmitter {
4528
5353
  * parser — and writing here as well produced a second row per turn. See done().
4529
5354
  */
4530
5355
  async sendMessageToSession(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
5356
+ const managed = (this.agentConfig.orchestration?.enabled && (this.agentConfig.orchestration.channels ?? ['api']).includes(channel)) ||
5357
+ (this.orchestration?.ownsSession(sessionId) && !this.orchestration.canReturnToLegacy());
5358
+ if (managed)
5359
+ return this.sendOrchestratedChannel(rawChatId, channel, sessionId, message, senderName, callbacks, opts);
4531
5360
  // Ensure the session process uses the correct channel source
4532
5361
  this.channelSourceMap.set(rawChatId, channel);
4533
5362
  // Channel sessions use agent-level model (not per-session)