@mono-agent/agent-app 0.11.2 → 0.11.4

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 (374) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +222 -26
  3. package/dist/adapter-send-tools-main.js +7 -1
  4. package/dist/adapter-send-tools-main.js.map +1 -1
  5. package/dist/adapter-send-tools.d.ts +19 -1
  6. package/dist/adapter-send-tools.d.ts.map +1 -1
  7. package/dist/adapter-send-tools.js +127 -13
  8. package/dist/adapter-send-tools.js.map +1 -1
  9. package/dist/app-config.d.ts.map +1 -1
  10. package/dist/app-config.js +15 -2
  11. package/dist/app-config.js.map +1 -1
  12. package/dist/app-controller-channels.d.ts +8 -0
  13. package/dist/app-controller-channels.d.ts.map +1 -0
  14. package/dist/app-controller-channels.js +207 -0
  15. package/dist/app-controller-channels.js.map +1 -0
  16. package/dist/app-controller-continuation.d.ts +26 -0
  17. package/dist/app-controller-continuation.d.ts.map +1 -0
  18. package/dist/app-controller-continuation.js +198 -0
  19. package/dist/app-controller-continuation.js.map +1 -0
  20. package/dist/app-controller-lifecycle.d.ts +6 -0
  21. package/dist/app-controller-lifecycle.d.ts.map +1 -0
  22. package/dist/app-controller-lifecycle.js +81 -0
  23. package/dist/app-controller-lifecycle.js.map +1 -0
  24. package/dist/app-controller-maintenance.d.ts +13 -0
  25. package/dist/app-controller-maintenance.d.ts.map +1 -0
  26. package/dist/app-controller-maintenance.js +180 -0
  27. package/dist/app-controller-maintenance.js.map +1 -0
  28. package/dist/app-controller-memory-health.d.ts +12 -0
  29. package/dist/app-controller-memory-health.d.ts.map +1 -0
  30. package/dist/app-controller-memory-health.js +160 -0
  31. package/dist/app-controller-memory-health.js.map +1 -0
  32. package/dist/app-controller-memory.d.ts +9 -0
  33. package/dist/app-controller-memory.d.ts.map +1 -0
  34. package/dist/app-controller-memory.js +83 -0
  35. package/dist/app-controller-memory.js.map +1 -0
  36. package/dist/app-controller-responder.d.ts +20 -0
  37. package/dist/app-controller-responder.d.ts.map +1 -0
  38. package/dist/app-controller-responder.js +246 -0
  39. package/dist/app-controller-responder.js.map +1 -0
  40. package/dist/app-controller-traceability.d.ts +25 -0
  41. package/dist/app-controller-traceability.d.ts.map +1 -0
  42. package/dist/app-controller-traceability.js +418 -0
  43. package/dist/app-controller-traceability.js.map +1 -0
  44. package/dist/app-controller-utils.d.ts +38 -0
  45. package/dist/app-controller-utils.d.ts.map +1 -0
  46. package/dist/app-controller-utils.js +185 -0
  47. package/dist/app-controller-utils.js.map +1 -0
  48. package/dist/app-controller.d.ts +400 -0
  49. package/dist/app-controller.d.ts.map +1 -0
  50. package/dist/app-controller.js +336 -0
  51. package/dist/app-controller.js.map +1 -0
  52. package/dist/app.d.ts +7 -115
  53. package/dist/app.d.ts.map +1 -1
  54. package/dist/app.js +6 -1912
  55. package/dist/app.js.map +1 -1
  56. package/dist/artifact-retention.d.ts +4 -0
  57. package/dist/artifact-retention.d.ts.map +1 -1
  58. package/dist/artifact-retention.js +38 -0
  59. package/dist/artifact-retention.js.map +1 -1
  60. package/dist/backfill.d.ts +3 -1
  61. package/dist/backfill.d.ts.map +1 -1
  62. package/dist/backfill.js +5 -1
  63. package/dist/backfill.js.map +1 -1
  64. package/dist/background-runtime.d.ts +27 -0
  65. package/dist/background-runtime.d.ts.map +1 -1
  66. package/dist/background-runtime.js +383 -205
  67. package/dist/background-runtime.js.map +1 -1
  68. package/dist/background-worker-lease.d.ts.map +1 -1
  69. package/dist/background-worker-lease.js +41 -241
  70. package/dist/background-worker-lease.js.map +1 -1
  71. package/dist/background.d.ts +31 -9
  72. package/dist/background.d.ts.map +1 -1
  73. package/dist/background.js +543 -272
  74. package/dist/background.js.map +1 -1
  75. package/dist/channel-drivers/cron.d.ts +11 -0
  76. package/dist/channel-drivers/cron.d.ts.map +1 -0
  77. package/dist/channel-drivers/cron.js +168 -0
  78. package/dist/channel-drivers/cron.js.map +1 -0
  79. package/dist/channel-drivers/live.d.ts +11 -0
  80. package/dist/channel-drivers/live.d.ts.map +1 -0
  81. package/dist/channel-drivers/live.js +47 -0
  82. package/dist/channel-drivers/live.js.map +1 -0
  83. package/dist/channel-drivers/native-notify.d.ts +27 -0
  84. package/dist/channel-drivers/native-notify.d.ts.map +1 -0
  85. package/dist/channel-drivers/native-notify.js +94 -0
  86. package/dist/channel-drivers/native-notify.js.map +1 -0
  87. package/dist/channel-drivers/openai-api.d.ts +7 -0
  88. package/dist/channel-drivers/openai-api.d.ts.map +1 -0
  89. package/dist/channel-drivers/openai-api.js +63 -0
  90. package/dist/channel-drivers/openai-api.js.map +1 -0
  91. package/dist/channel-drivers/shared.d.ts +4 -0
  92. package/dist/channel-drivers/shared.d.ts.map +1 -0
  93. package/dist/channel-drivers/shared.js +5 -0
  94. package/dist/channel-drivers/shared.js.map +1 -0
  95. package/dist/channel-drivers/slack.d.ts +14 -0
  96. package/dist/channel-drivers/slack.d.ts.map +1 -0
  97. package/dist/channel-drivers/slack.js +227 -0
  98. package/dist/channel-drivers/slack.js.map +1 -0
  99. package/dist/channel-drivers/telegram.d.ts +11 -0
  100. package/dist/channel-drivers/telegram.d.ts.map +1 -0
  101. package/dist/channel-drivers/telegram.js +244 -0
  102. package/dist/channel-drivers/telegram.js.map +1 -0
  103. package/dist/channel-drivers/tui.d.ts +15 -0
  104. package/dist/channel-drivers/tui.d.ts.map +1 -0
  105. package/dist/channel-drivers/tui.js +108 -0
  106. package/dist/channel-drivers/tui.js.map +1 -0
  107. package/dist/channel-drivers/webhook.d.ts +7 -0
  108. package/dist/channel-drivers/webhook.d.ts.map +1 -0
  109. package/dist/channel-drivers/webhook.js +115 -0
  110. package/dist/channel-drivers/webhook.js.map +1 -0
  111. package/dist/channels.d.ts +21 -81
  112. package/dist/channels.d.ts.map +1 -1
  113. package/dist/channels.js +11 -1174
  114. package/dist/channels.js.map +1 -1
  115. package/dist/cli-args.d.ts +121 -0
  116. package/dist/cli-args.d.ts.map +1 -0
  117. package/dist/cli-args.js +532 -0
  118. package/dist/cli-args.js.map +1 -0
  119. package/dist/cli-background-command.d.ts +59 -0
  120. package/dist/cli-background-command.d.ts.map +1 -0
  121. package/dist/cli-background-command.js +475 -0
  122. package/dist/cli-background-command.js.map +1 -0
  123. package/dist/cli-cancellation.d.ts +6 -0
  124. package/dist/cli-cancellation.d.ts.map +1 -0
  125. package/dist/cli-cancellation.js +23 -0
  126. package/dist/cli-cancellation.js.map +1 -0
  127. package/dist/cli-commands.d.ts +12 -0
  128. package/dist/cli-commands.d.ts.map +1 -0
  129. package/dist/cli-commands.js +306 -0
  130. package/dist/cli-commands.js.map +1 -0
  131. package/dist/cli-help.d.ts +11 -0
  132. package/dist/cli-help.d.ts.map +1 -0
  133. package/dist/cli-help.js +262 -0
  134. package/dist/cli-help.js.map +1 -0
  135. package/dist/cli-init-command.d.ts +60 -0
  136. package/dist/cli-init-command.d.ts.map +1 -0
  137. package/dist/cli-init-command.js +1910 -0
  138. package/dist/cli-init-command.js.map +1 -0
  139. package/dist/cli-install-skill-command.d.ts +3 -0
  140. package/dist/cli-install-skill-command.d.ts.map +1 -0
  141. package/dist/cli-install-skill-command.js +51 -0
  142. package/dist/cli-install-skill-command.js.map +1 -0
  143. package/dist/cli-sandbox-command.d.ts +10 -0
  144. package/dist/cli-sandbox-command.d.ts.map +1 -0
  145. package/dist/cli-sandbox-command.js +100 -0
  146. package/dist/cli-sandbox-command.js.map +1 -0
  147. package/dist/cli-validate-config-command.d.ts +36 -0
  148. package/dist/cli-validate-config-command.d.ts.map +1 -0
  149. package/dist/cli-validate-config-command.js +317 -0
  150. package/dist/cli-validate-config-command.js.map +1 -0
  151. package/dist/cli.d.ts +6 -248
  152. package/dist/cli.d.ts.map +1 -1
  153. package/dist/cli.js +6 -3792
  154. package/dist/cli.js.map +1 -1
  155. package/dist/config-reference.d.ts +2 -1
  156. package/dist/config-reference.d.ts.map +1 -1
  157. package/dist/config-reference.js +34 -8
  158. package/dist/config-reference.js.map +1 -1
  159. package/dist/configured-agent.d.ts +23 -0
  160. package/dist/configured-agent.d.ts.map +1 -1
  161. package/dist/configured-agent.js +92 -13
  162. package/dist/configured-agent.js.map +1 -1
  163. package/dist/continuation-dispatch-worker.d.ts +21 -0
  164. package/dist/continuation-dispatch-worker.d.ts.map +1 -0
  165. package/dist/continuation-dispatch-worker.js +425 -0
  166. package/dist/continuation-dispatch-worker.js.map +1 -0
  167. package/dist/continuation-http-routes.d.ts +15 -0
  168. package/dist/continuation-http-routes.d.ts.map +1 -0
  169. package/dist/continuation-http-routes.js +265 -0
  170. package/dist/continuation-http-routes.js.map +1 -0
  171. package/dist/continuation-operator-api.d.ts +18 -0
  172. package/dist/continuation-operator-api.d.ts.map +1 -0
  173. package/dist/continuation-operator-api.js +164 -0
  174. package/dist/continuation-operator-api.js.map +1 -0
  175. package/dist/continuation-origin-store.d.ts +19 -0
  176. package/dist/continuation-origin-store.d.ts.map +1 -0
  177. package/dist/continuation-origin-store.js +273 -0
  178. package/dist/continuation-origin-store.js.map +1 -0
  179. package/dist/continuation-service-errors.d.ts +18 -0
  180. package/dist/continuation-service-errors.d.ts.map +1 -0
  181. package/dist/continuation-service-errors.js +35 -0
  182. package/dist/continuation-service-errors.js.map +1 -0
  183. package/dist/continuation-service-helpers.d.ts +39 -0
  184. package/dist/continuation-service-helpers.d.ts.map +1 -0
  185. package/dist/continuation-service-helpers.js +308 -0
  186. package/dist/continuation-service-helpers.js.map +1 -0
  187. package/dist/continuation-service-origin.d.ts +55 -0
  188. package/dist/continuation-service-origin.d.ts.map +1 -0
  189. package/dist/continuation-service-origin.js +325 -0
  190. package/dist/continuation-service-origin.js.map +1 -0
  191. package/dist/continuation-service-types.d.ts +110 -0
  192. package/dist/continuation-service-types.d.ts.map +1 -0
  193. package/dist/continuation-service-types.js +11 -0
  194. package/dist/continuation-service-types.js.map +1 -0
  195. package/dist/continuation-service.d.ts +4 -97
  196. package/dist/continuation-service.d.ts.map +1 -1
  197. package/dist/continuation-service.js +9 -1525
  198. package/dist/continuation-service.js.map +1 -1
  199. package/dist/continuation-store-fs.d.ts +15 -0
  200. package/dist/continuation-store-fs.d.ts.map +1 -0
  201. package/dist/continuation-store-fs.js +247 -0
  202. package/dist/continuation-store-fs.js.map +1 -0
  203. package/dist/continuation-store-policy.d.ts +21 -0
  204. package/dist/continuation-store-policy.d.ts.map +1 -0
  205. package/dist/continuation-store-policy.js +278 -0
  206. package/dist/continuation-store-policy.js.map +1 -0
  207. package/dist/continuation-store-records.d.ts +9 -0
  208. package/dist/continuation-store-records.d.ts.map +1 -0
  209. package/dist/continuation-store-records.js +221 -0
  210. package/dist/continuation-store-records.js.map +1 -0
  211. package/dist/continuation-store-types.d.ts +173 -0
  212. package/dist/continuation-store-types.d.ts.map +1 -0
  213. package/dist/continuation-store-types.js +26 -0
  214. package/dist/continuation-store-types.js.map +1 -0
  215. package/dist/continuation-store.d.ts +4 -138
  216. package/dist/continuation-store.d.ts.map +1 -1
  217. package/dist/continuation-store.js +145 -1049
  218. package/dist/continuation-store.js.map +1 -1
  219. package/dist/doctor.d.ts +10 -5
  220. package/dist/doctor.d.ts.map +1 -1
  221. package/dist/doctor.js +629 -58
  222. package/dist/doctor.js.map +1 -1
  223. package/dist/init.d.ts +2 -0
  224. package/dist/init.d.ts.map +1 -1
  225. package/dist/init.js +92 -239
  226. package/dist/init.js.map +1 -1
  227. package/dist/interaction-bridge.d.ts +19 -0
  228. package/dist/interaction-bridge.d.ts.map +1 -1
  229. package/dist/interaction-bridge.js +130 -0
  230. package/dist/interaction-bridge.js.map +1 -1
  231. package/dist/launchd-logs.d.ts +91 -0
  232. package/dist/launchd-logs.d.ts.map +1 -0
  233. package/dist/launchd-logs.js +1499 -0
  234. package/dist/launchd-logs.js.map +1 -0
  235. package/dist/launchd.d.ts +27 -0
  236. package/dist/launchd.d.ts.map +1 -1
  237. package/dist/launchd.js +89 -10
  238. package/dist/launchd.js.map +1 -1
  239. package/dist/memory-command.d.ts +2 -0
  240. package/dist/memory-command.d.ts.map +1 -1
  241. package/dist/memory-command.js +161 -48
  242. package/dist/memory-command.js.map +1 -1
  243. package/dist/memory-recall-settings.d.ts +64 -0
  244. package/dist/memory-recall-settings.d.ts.map +1 -0
  245. package/dist/memory-recall-settings.js +51 -0
  246. package/dist/memory-recall-settings.js.map +1 -0
  247. package/dist/memory-recall.d.ts +7 -95
  248. package/dist/memory-recall.d.ts.map +1 -1
  249. package/dist/memory-recall.js +8 -143
  250. package/dist/memory-recall.js.map +1 -1
  251. package/dist/memory-rituals.d.ts +3 -3
  252. package/dist/memory-rituals.d.ts.map +1 -1
  253. package/dist/memory-rituals.js +16 -111
  254. package/dist/memory-rituals.js.map +1 -1
  255. package/dist/modules/catalog.js +2 -2
  256. package/dist/modules/catalog.js.map +1 -1
  257. package/dist/modules/known-tools.d.ts +8 -6
  258. package/dist/modules/known-tools.d.ts.map +1 -1
  259. package/dist/modules/known-tools.js +8 -6
  260. package/dist/modules/known-tools.js.map +1 -1
  261. package/dist/notify-destinations.d.ts +5 -0
  262. package/dist/notify-destinations.d.ts.map +1 -1
  263. package/dist/notify-destinations.js +7 -1
  264. package/dist/notify-destinations.js.map +1 -1
  265. package/dist/owner-private-lock.d.ts +95 -0
  266. package/dist/owner-private-lock.d.ts.map +1 -0
  267. package/dist/owner-private-lock.js +566 -0
  268. package/dist/owner-private-lock.js.map +1 -0
  269. package/dist/pi-oauth-login.d.ts +2 -0
  270. package/dist/pi-oauth-login.d.ts.map +1 -1
  271. package/dist/pi-oauth-login.js +39 -5
  272. package/dist/pi-oauth-login.js.map +1 -1
  273. package/dist/posted-message-index.d.ts +35 -16
  274. package/dist/posted-message-index.d.ts.map +1 -1
  275. package/dist/posted-message-index.js +1068 -52
  276. package/dist/posted-message-index.js.map +1 -1
  277. package/dist/posted-reply-history.d.ts +17 -0
  278. package/dist/posted-reply-history.d.ts.map +1 -0
  279. package/dist/posted-reply-history.js +169 -0
  280. package/dist/posted-reply-history.js.map +1 -0
  281. package/dist/proactive-notify.d.ts.map +1 -1
  282. package/dist/proactive-notify.js +6 -4
  283. package/dist/proactive-notify.js.map +1 -1
  284. package/dist/project-skills.d.ts +4 -0
  285. package/dist/project-skills.d.ts.map +1 -1
  286. package/dist/project-skills.js +99 -130
  287. package/dist/project-skills.js.map +1 -1
  288. package/dist/provider-setup.d.ts +0 -1
  289. package/dist/provider-setup.d.ts.map +1 -1
  290. package/dist/provider-setup.js +0 -3
  291. package/dist/provider-setup.js.map +1 -1
  292. package/dist/readiness-probe-worker.d.ts +61 -1
  293. package/dist/readiness-probe-worker.d.ts.map +1 -1
  294. package/dist/readiness-probe-worker.js +54 -58
  295. package/dist/readiness-probe-worker.js.map +1 -1
  296. package/dist/readiness-probe.d.ts +6 -9
  297. package/dist/readiness-probe.d.ts.map +1 -1
  298. package/dist/readiness-probe.js +30 -56
  299. package/dist/readiness-probe.js.map +1 -1
  300. package/dist/redact-secrets.d.ts +15 -0
  301. package/dist/redact-secrets.d.ts.map +1 -0
  302. package/dist/redact-secrets.js +65 -0
  303. package/dist/redact-secrets.js.map +1 -0
  304. package/dist/run-history.js +34 -24
  305. package/dist/run-history.js.map +1 -1
  306. package/dist/runtime-provenance.d.ts +8 -0
  307. package/dist/runtime-provenance.d.ts.map +1 -0
  308. package/dist/runtime-provenance.js +225 -0
  309. package/dist/runtime-provenance.js.map +1 -0
  310. package/dist/sandbox-manager.d.ts +4 -1
  311. package/dist/sandbox-manager.d.ts.map +1 -1
  312. package/dist/sandbox-manager.js +114 -273
  313. package/dist/sandbox-manager.js.map +1 -1
  314. package/dist/secure-file-replace.d.ts +49 -0
  315. package/dist/secure-file-replace.d.ts.map +1 -0
  316. package/dist/secure-file-replace.js +270 -0
  317. package/dist/secure-file-replace.js.map +1 -0
  318. package/dist/seen-conversations.d.ts +32 -5
  319. package/dist/seen-conversations.d.ts.map +1 -1
  320. package/dist/seen-conversations.js +145 -15
  321. package/dist/seen-conversations.js.map +1 -1
  322. package/dist/sessions.d.ts +20 -0
  323. package/dist/sessions.d.ts.map +1 -1
  324. package/dist/sessions.js +41 -6
  325. package/dist/sessions.js.map +1 -1
  326. package/dist/wizard/index.d.ts +2 -2
  327. package/dist/wizard/index.d.ts.map +1 -1
  328. package/dist/wizard/index.js +1 -1
  329. package/dist/wizard/index.js.map +1 -1
  330. package/dist/wizard/presets.d.ts +2 -1
  331. package/dist/wizard/presets.d.ts.map +1 -1
  332. package/dist/wizard/presets.js +2 -1
  333. package/dist/wizard/presets.js.map +1 -1
  334. package/dist/wizard/run.d.ts +0 -26
  335. package/dist/wizard/run.d.ts.map +1 -1
  336. package/dist/wizard/run.js +0 -53
  337. package/dist/wizard/run.js.map +1 -1
  338. package/package.json +22 -17
  339. package/schema/mono-agent.config.schema.json +45 -10
  340. package/skills/mono-agent-composer/SKILL.md +3 -3
  341. package/skills/mono-agent-composer/references/config-blueprint.md +123 -29
  342. package/skills/mono-agent-composer/references/discovery-questions.md +10 -8
  343. package/skills/mono-agent-composer/references/feature-coverage.md +90 -76
  344. package/skills/mono-agent-composer/references/package-map.md +6 -4
  345. package/skills/mono-agent-composer/references/playbooks.md +48 -27
  346. package/skills/mono-agent-composer/references/validation.md +16 -3
  347. package/dist/notify-runtime.d.ts +0 -26
  348. package/dist/notify-runtime.d.ts.map +0 -1
  349. package/dist/notify-runtime.js +0 -27
  350. package/dist/notify-runtime.js.map +0 -1
  351. package/dist/notify-tool.d.ts +0 -51
  352. package/dist/notify-tool.d.ts.map +0 -1
  353. package/dist/notify-tool.js +0 -182
  354. package/dist/notify-tool.js.map +0 -1
  355. package/dist/recipes/base.d.ts +0 -15
  356. package/dist/recipes/base.d.ts.map +0 -1
  357. package/dist/recipes/base.js +0 -51
  358. package/dist/recipes/base.js.map +0 -1
  359. package/dist/recipes/catalog.d.ts +0 -4
  360. package/dist/recipes/catalog.d.ts.map +0 -1
  361. package/dist/recipes/catalog.js +0 -525
  362. package/dist/recipes/catalog.js.map +0 -1
  363. package/dist/recipes/index.d.ts +0 -11
  364. package/dist/recipes/index.d.ts.map +0 -1
  365. package/dist/recipes/index.js +0 -14
  366. package/dist/recipes/index.js.map +0 -1
  367. package/dist/recipes/types.d.ts +0 -70
  368. package/dist/recipes/types.d.ts.map +0 -1
  369. package/dist/recipes/types.js +0 -15
  370. package/dist/recipes/types.js.map +0 -1
  371. package/dist/setup.d.ts +0 -29
  372. package/dist/setup.d.ts.map +0 -1
  373. package/dist/setup.js +0 -97
  374. package/dist/setup.js.map +0 -1
package/dist/channels.js CHANGED
@@ -1,15 +1,12 @@
1
- import { NOTHING_TO_REPORT_SENTINEL } from "@mono-agent/agent-contracts";
2
- import { AgentHarnessFailureError } from "@mono-agent/agent-harness";
3
- import { describeRunFailureKind } from "@mono-agent/observability";
4
- import { discoverLocalProviderModels, modelReferenceKey, parseMonoRuntimeModelReference, resolveModelEffortLevels, } from "@mono-agent/runtime-adapter";
5
- import { isAdapterSendToolAllowed } from "./adapter-send-tools.js";
6
- import { isChannelConfigured } from "./channel-gate.js";
7
- import { buildChannelConfigView } from "./channel-config-view.js";
8
- import { resolveConfiguredChannelPlugins, } from "./channel-plugins.js";
9
- import { findTriggerOverrideIssues } from "./trigger-overrides.js";
10
- import { appendPostedMessage, lookupProducingConversation } from "./posted-message-index.js";
11
- import { configuredRuntimeModels } from "./runtime-routes.js";
12
- /** The channel ids this app can drive from config (third-party drivers add their own). */
1
+ import { createCronChannelDriver } from "./channel-drivers/cron.js";
2
+ import { createLiveChannelDriver } from "./channel-drivers/live.js";
3
+ import { createOpenAIApiChannelDriver } from "./channel-drivers/openai-api.js";
4
+ import { createSlackChannelDriver, slackTargetFromConversation } from "./channel-drivers/slack.js";
5
+ import { createTelegramChannelDriver, telegramChatIdFromConversation } from "./channel-drivers/telegram.js";
6
+ import { createTuiChannelDriver } from "./channel-drivers/tui.js";
7
+ import { createWebhookChannelDriver } from "./channel-drivers/webhook.js";
8
+ import { resolveConfiguredChannelPlugins } from "./channel-plugins.js";
9
+ /** Built-in channel ids in startup and status-display order. */
13
10
  export const BUILTIN_CHANNEL_IDS = [
14
11
  "telegram",
15
12
  "slack",
@@ -19,1045 +16,8 @@ export const BUILTIN_CHANNEL_IDS = [
19
16
  "tui",
20
17
  "live",
21
18
  ];
22
- let telegramModule;
23
- let slackModule;
24
- let webhookModule;
25
- let openaiApiModule;
26
- let cronModule;
27
- let tuiModule;
28
- const loadTelegramModule = async () => (telegramModule ??= await import("@mono-agent/telegram-adapter"));
29
- const loadSlackModule = async () => (slackModule ??= await import("@mono-agent/slack-adapter"));
30
- const loadWebhookModule = async () => (webhookModule ??= await import("@mono-agent/webhook-adapter"));
31
- const loadOpenAIApiModule = async () => (openaiApiModule ??= await import("@mono-agent/openai-api-adapter"));
32
- const loadCronModule = async () => (cronModule ??= await import("@mono-agent/cron-adapter"));
33
- const loadTuiModule = async () => (tuiModule ??= await import("@mono-agent/operator-adapter"));
34
- let liveModule;
35
- const loadLiveModule = async () => (liveModule ??= await import("@mono-agent/operator-adapter"));
36
- const TELEGRAM_GATE = { jsonKey: "telegram", envPrefix: "MONO_AGENT_TELEGRAM_" };
37
- const SLACK_GATE = { jsonKey: "slack", envPrefix: "MONO_AGENT_SLACK_" };
38
- const WEBHOOK_GATE = { jsonKey: "webhook", envPrefix: "MONO_AGENT_WEBHOOK_", dir: "webhook" };
39
- const OPENAI_API_GATE = { jsonKey: "openaiApi", envPrefix: "MONO_AGENT_OPENAI_API_" };
40
- const CRON_GATE = { jsonKey: "cron", envPrefix: "MONO_AGENT_CRON_", dir: "cron" };
41
- const UNCONFIGURED_TELEGRAM_CONFIG = {
42
- enabled: false,
43
- botToken: "",
44
- allowedChatIds: [],
45
- allowAllChats: false,
46
- };
47
- const UNCONFIGURED_SLACK_CONFIG = {
48
- enabled: false,
49
- botToken: "",
50
- appToken: "",
51
- allowedChannelIds: [],
52
- allowAllChannels: false,
53
- botUserIds: [],
54
- mentionTextAliases: [],
55
- stripMentionText: false,
56
- shortcuts: [],
57
- homeTab: { enabled: false, buttons: [] },
58
- };
59
- const UNCONFIGURED_WEBHOOK_CONFIG = {
60
- enabled: false,
61
- host: "127.0.0.1",
62
- port: 0,
63
- allowNonLoopback: false,
64
- retentionMs: 300_000,
65
- maxStoredRequests: 100,
66
- endpoints: [{ name: "default", path: "/webhook/invoke", mode: "sync", enabled: true }],
67
- path: "/webhook/invoke",
68
- defaultMode: "sync",
69
- };
70
- const UNCONFIGURED_OPENAI_API_CONFIG = {
71
- enabled: false,
72
- host: "127.0.0.1",
73
- port: 0,
74
- basePath: "/v1",
75
- allowNonLoopback: false,
76
- modelId: "agent",
77
- };
78
- const UNCONFIGURED_CRON_CONFIG = { jobs: [] };
79
- /** Config-view section for a channel the gate found no intent for (no fields to annotate). */
80
- function unconfiguredChannelView(id, label) {
81
- return { id, label, status: "disabled", fields: [] };
82
- }
83
- export function createTelegramChannelDriver(overrides = {}) {
84
- return {
85
- id: "telegram",
86
- label: "Telegram",
87
- async configView(input) {
88
- if (!(await isChannelConfigured(input, TELEGRAM_GATE))) {
89
- return unconfiguredChannelView("telegram", "Telegram");
90
- }
91
- const adapter = await loadTelegramModule();
92
- return await buildChannelConfigView(this, adapter.TELEGRAM_CONFIG_FIELDS, input);
93
- },
94
- async loadConfig(input) {
95
- if (!(await isChannelConfigured(input, TELEGRAM_GATE))) {
96
- return UNCONFIGURED_TELEGRAM_CONFIG;
97
- }
98
- const adapter = await loadTelegramModule();
99
- return await adapter.loadTelegramAdapterConfig({ env: input.env, jsonPath: input.configPath });
100
- },
101
- isConfigError(error) {
102
- return telegramModule !== undefined && error instanceof telegramModule.TelegramAdapterConfigError;
103
- },
104
- disabledReason(config) {
105
- return config.enabled ? undefined : "Telegram is disabled.";
106
- },
107
- async start(input) {
108
- const adapter = await loadTelegramModule();
109
- const startAdapter = overrides.startAdapter ?? adapter.startTelegramAdapter;
110
- const result = await startAdapter(telegramStartOptions(input, overrides));
111
- // Register this channel's interaction sink so the host bridge can post
112
- // AskUser questions and tool-progress status lines into Telegram chats.
113
- // The sink enforces the adapter allowlist: a tool-supplied conversation
114
- // can never reach a chat the operator never allowlisted.
115
- input.interaction?.registerSink("telegram", {
116
- postQuestion: async (conversationId, text) => {
117
- await result.post(requireAllowedTelegramChat(conversationId, input), text);
118
- },
119
- postStatus: async (conversationId, text, statusOptions) => {
120
- await result.postStatus(requireAllowedTelegramChat(conversationId, input), text, statusOptions);
121
- },
122
- });
123
- return {
124
- summary: {},
125
- stop: () => result.stop(),
126
- // Push delivery: a proactive nudge to telegram:<chat> runs as a turn on
127
- // this chat's own harness and is delivered through the normal stream.
128
- // Enforces the adapter allowlist so a payload-supplied destination cannot
129
- // reach a chat the operator never allowlisted.
130
- notify: async (request) => {
131
- const { conversationId, text, verbatim } = request;
132
- const chatId = telegramChatIdFromConversation(conversationId);
133
- if (chatId === undefined) {
134
- input.logger?.warn?.("Telegram proactive notify skipped: unparseable destination.", { conversationId });
135
- return { delivered: false, reason: "unparseable telegram destination" };
136
- }
137
- if (!input.config.allowAllChats && !input.config.allowedChatIds.includes(String(chatId))) {
138
- input.logger?.warn?.("Telegram proactive notify skipped: destination not in allowlist.", { conversationId });
139
- return { delivered: false, reason: "telegram chat is not in the adapter allowlist" };
140
- }
141
- // During configured quiet hours, deliver silently (disable_notification)
142
- // so an overnight cron/webhook result lands without a push sound.
143
- const silent = input.config.quietHours !== undefined &&
144
- adapter.isWithinQuietHours(new Date(), input.config.quietHours);
145
- const notifyOptions = verbatim === undefined && !silent
146
- ? undefined
147
- : {
148
- ...(verbatim === undefined ? {} : { verbatim }),
149
- ...(silent ? { silent: true } : {}),
150
- };
151
- return await result.notify(chatId, text, notifyOptions);
152
- },
153
- };
154
- },
155
- };
156
- }
157
- /**
158
- * Download-path attachment options from config (`maxUploadBytes` stays with the
159
- * send tools, which reload the config themselves). Auto-transcription config is
160
- * forwarded here too so inbound audio gets an inlined transcript.
161
- */
162
- function telegramAttachmentOptions(config) {
163
- const attachments = config.attachments;
164
- const transcription = config.transcription;
165
- if (attachments?.maxBytes === undefined &&
166
- attachments?.downloadTimeoutMs === undefined &&
167
- transcription === undefined) {
168
- return undefined;
169
- }
170
- return {
171
- ...(attachments?.maxBytes === undefined ? {} : { maxBytes: attachments.maxBytes }),
172
- ...(attachments?.downloadTimeoutMs === undefined ? {} : { downloadTimeoutMs: attachments.downloadTimeoutMs }),
173
- ...(transcription === undefined ? {} : { transcription }),
174
- };
175
- }
176
- /** Resolve + allowlist-check a `telegram:<chat>` destination for interaction-sink posts. */
177
- function requireAllowedTelegramChat(conversationId, input) {
178
- const chatId = telegramChatIdFromConversation(conversationId);
179
- if (chatId === undefined) {
180
- throw new Error(`unparseable telegram destination: ${conversationId}`);
181
- }
182
- if (!input.config.allowAllChats && !input.config.allowedChatIds.includes(String(chatId))) {
183
- throw new Error("telegram chat is not in the adapter allowlist.");
184
- }
185
- return chatId;
186
- }
187
- /** Extract the Telegram chat id from a `telegram:<chat>` conversationId (numeric ids become numbers; a rollover #bucket suffix is stripped). */
188
- export function telegramChatIdFromConversation(conversationId) {
189
- const prefix = "telegram:";
190
- if (!conversationId.startsWith(prefix)) {
191
- return undefined;
192
- }
193
- const raw = conversationId.slice(prefix.length).split("#", 1)[0]?.trim();
194
- if (raw === undefined || raw.length === 0) {
195
- return undefined;
196
- }
197
- return /^-?\d+$/u.test(raw) ? Number(raw) : raw;
198
- }
199
- export function createSlackChannelDriver(overrides = {}) {
200
- return {
201
- id: "slack",
202
- label: "Slack",
203
- async configView(input) {
204
- if (!(await isChannelConfigured(input, SLACK_GATE))) {
205
- return unconfiguredChannelView("slack", "Slack");
206
- }
207
- const adapter = await loadSlackModule();
208
- return await buildChannelConfigView(this, adapter.SLACK_CONFIG_FIELDS, input);
209
- },
210
- async loadConfig(input) {
211
- if (!(await isChannelConfigured(input, SLACK_GATE))) {
212
- return UNCONFIGURED_SLACK_CONFIG;
213
- }
214
- const adapter = await loadSlackModule();
215
- return await adapter.loadSlackAdapterConfig({ env: input.env, jsonPath: input.configPath });
216
- },
217
- isConfigError(error) {
218
- return slackModule !== undefined && error instanceof slackModule.SlackAdapterConfigError;
219
- },
220
- disabledReason(config) {
221
- return config.enabled ? undefined : "Slack is disabled.";
222
- },
223
- async start(input) {
224
- const adapter = await loadSlackModule();
225
- const startAdapter = overrides.startAdapter ?? adapter.startSlackAdapter;
226
- // Link posted messages to their producing conversation so an in-thread reply
227
- // resumes that conversation instead of a fresh, history-less slack: thread.
228
- const indexPath = input.postedMessageIndexPath;
229
- // Forward the operator's optional Socket Mode resilience tuning; omitting a
230
- // field lets the runner apply its own default.
231
- const reconnect = {};
232
- if (input.config.reconnectInitialBackoffMs !== undefined)
233
- reconnect.initialMs = input.config.reconnectInitialBackoffMs;
234
- if (input.config.reconnectMaxBackoffMs !== undefined)
235
- reconnect.maxMs = input.config.reconnectMaxBackoffMs;
236
- if (input.config.reconnectStabilityMs !== undefined)
237
- reconnect.stabilityMs = input.config.reconnectStabilityMs;
238
- if (input.config.reconnectStartupGraceMs !== undefined)
239
- reconnect.startupGraceMs = input.config.reconnectStartupGraceMs;
240
- if (input.config.drainDeadlineMs !== undefined)
241
- reconnect.drainDeadlineMs = input.config.drainDeadlineMs;
242
- const heartbeat = {};
243
- if (input.config.heartbeatIntervalMs !== undefined)
244
- heartbeat.intervalMs = input.config.heartbeatIntervalMs;
245
- if (input.config.heartbeatTimeoutMs !== undefined)
246
- heartbeat.timeoutMs = input.config.heartbeatTimeoutMs;
247
- const result = await startAdapter({
248
- botToken: input.config.botToken,
249
- appToken: input.config.appToken,
250
- allowedChannelIds: input.config.allowedChannelIds,
251
- allowAllChannels: input.config.allowAllChannels,
252
- botUserIds: input.config.botUserIds,
253
- mentionTextAliases: input.config.mentionTextAliases,
254
- stripMentionText: input.config.stripMentionText,
255
- shortcuts: input.config.shortcuts,
256
- homeTab: input.config.homeTab,
257
- responder: input.responder,
258
- // A Socket Mode drop is recoverable by construction (the runner always
259
- // reconnects with backoff), so report it as DEGRADED — keep the responder/
260
- // harness alive — rather than fatal. onConnectionRestored flips the channel
261
- // back to running once a reconnect stays up past the stability window.
262
- onConnectionLost: (reason) => input.onDegraded?.(reason),
263
- onConnectionRestored: () => input.onRecovered?.(),
264
- ...(Object.keys(reconnect).length === 0 ? {} : { reconnect }),
265
- ...(Object.keys(heartbeat).length === 0 ? {} : { heartbeat }),
266
- ...(input.logger === undefined ? {} : { logger: input.logger }),
267
- ...(indexPath === undefined
268
- ? {}
269
- : {
270
- resolvePostIndex: (channelId, ts) => lookupProducingConversation(indexPath, channelId, ts),
271
- recordPostedMessage: (channelId, ts, conversationId) => {
272
- void appendPostedMessage(indexPath, { channelId, ts, conversationId });
273
- },
274
- }),
275
- ...(overrides.createApi === undefined ? {} : { createApi: overrides.createApi }),
276
- ...(overrides.webSocketFactory === undefined ? {} : { webSocketFactory: overrides.webSocketFactory }),
277
- });
278
- return {
279
- summary: {},
280
- stop: () => result.stop(),
281
- // Push delivery: a proactive nudge to slack:<ch>[:<thread>] runs as a turn
282
- // on that conversation's own harness and is delivered through the stream.
283
- // Enforces the adapter allowlist so a payload-supplied destination cannot
284
- // reach a channel the operator never allowlisted.
285
- notify: async (request) => {
286
- const { conversationId, text, verbatim } = request;
287
- const { deliveryKey } = request;
288
- const target = slackTargetFromConversation(conversationId);
289
- if (target === undefined) {
290
- input.logger?.warn?.("Slack proactive notify skipped: unparseable destination.", { conversationId });
291
- return { delivered: false, reason: "unparseable slack destination" };
292
- }
293
- const normalized = target.channelId.trim().toLowerCase();
294
- const allowed = input.config.allowAllChannels ||
295
- input.config.allowedChannelIds.some((id) => id.trim().toLowerCase() === normalized);
296
- if (!allowed) {
297
- input.logger?.warn?.("Slack proactive notify skipped: destination not in allowlist.", { conversationId });
298
- return { delivered: false, reason: "slack channel is not in the adapter allowlist" };
299
- }
300
- return await result.adapter.notify(target.channelId, target.threadTs, text, verbatim === undefined && deliveryKey === undefined
301
- ? undefined
302
- : {
303
- ...(verbatim === undefined ? {} : { verbatim }),
304
- ...(deliveryKey === undefined ? {} : { deliveryKey }),
305
- });
306
- },
307
- synthesizeContinuation: async (continuationInput) => {
308
- const target = slackTargetFromConversation(continuationInput.replyToConversationId);
309
- if (target === undefined)
310
- throw new Error("Unparseable Slack continuation destination.");
311
- const normalized = target.channelId.trim().toLowerCase();
312
- const allowed = input.config.allowAllChannels
313
- || input.config.allowedChannelIds.some((id) => id.trim().toLowerCase() === normalized);
314
- if (!allowed)
315
- throw new Error("Slack continuation destination is not in the adapter allowlist.");
316
- try {
317
- const continuation = continuationInput.originContextPolicy === "pinned"
318
- ? (() => {
319
- if (continuationInput.historyBoundary === undefined || continuationInput.originContext === undefined) {
320
- throw new Error("Pinned Slack continuation input is missing its immutable origin context.");
321
- }
322
- return {
323
- continuationId: continuationInput.continuationId,
324
- originRunId: continuationInput.originRunId,
325
- historyBoundary: continuationInput.historyBoundary,
326
- originContextPolicy: "pinned",
327
- originContext: continuationInput.originContext,
328
- toolsDisabled: true,
329
- deferHistoryCommit: true,
330
- };
331
- })()
332
- : {
333
- continuationId: continuationInput.continuationId,
334
- originRunId: continuationInput.originRunId,
335
- originContextPolicy: "detached_latest",
336
- toolsDisabled: true,
337
- deferHistoryCommit: true,
338
- };
339
- const text = await result.adapter.synthesizeContinuation({
340
- conversationId: continuationInput.originConversationId,
341
- replyToConversationId: continuationInput.replyToConversationId,
342
- channelId: target.channelId,
343
- ...(target.threadTs === undefined ? {} : { threadTs: target.threadTs }),
344
- prompt: continuationInput.prompt,
345
- continuation,
346
- });
347
- return { kind: "synthesized", text };
348
- }
349
- catch (error) {
350
- if (error instanceof adapter.SerialQueueFullError) {
351
- return {
352
- kind: "unavailable",
353
- code: "destination_queue_full",
354
- reason: error.message,
355
- retryAfterMs: 1_000,
356
- };
357
- }
358
- if (error instanceof AgentHarnessFailureError && error.failure.kind === "history_boundary_not_found") {
359
- return {
360
- kind: "unavailable",
361
- code: "origin_history_not_ready",
362
- reason: "The originating run has not committed its continuation history boundary yet.",
363
- retryAfterMs: 1_000,
364
- };
365
- }
366
- throw error;
367
- }
368
- },
369
- recordContinuationHistory: async (continuationInput) => {
370
- const target = slackTargetFromConversation(continuationInput.conversationId);
371
- if (target === undefined)
372
- return { recorded: false, code: "unparseable_slack_destination" };
373
- const normalized = target.channelId.trim().toLowerCase();
374
- const allowed = input.config.allowAllChannels
375
- || input.config.allowedChannelIds.some((id) => id.trim().toLowerCase() === normalized);
376
- if (!allowed)
377
- return { recorded: false, code: "slack_destination_not_allowlisted" };
378
- return await result.adapter.recordContinuationHistory(continuationInput.conversationId, continuationInput.text, continuationInput.deliveryKey);
379
- },
380
- };
381
- },
382
- };
383
- }
384
- /** Extract `{channelId, threadTs?}` from a `slack:<ch>[:<thread>]` conversationId (a rollover #bucket suffix is stripped). */
385
- export function slackTargetFromConversation(conversationId) {
386
- const prefix = "slack:";
387
- if (!conversationId.startsWith(prefix)) {
388
- return undefined;
389
- }
390
- const rest = conversationId.slice(prefix.length).split("#", 1)[0];
391
- if (rest === undefined || rest.length === 0) {
392
- return undefined;
393
- }
394
- const colon = rest.indexOf(":");
395
- if (colon < 0) {
396
- const channelId = rest.trim();
397
- if (channelId.length === 0) {
398
- return undefined;
399
- }
400
- return { channelId };
401
- }
402
- const channelId = rest.slice(0, colon).trim();
403
- const threadTs = rest.slice(colon + 1).trim();
404
- if (channelId.length === 0) {
405
- return undefined;
406
- }
407
- if (threadTs.length === 0) {
408
- return { channelId };
409
- }
410
- // A canonical Slack threadTs (e.g. 1718800000.123456) never contains a colon, so a
411
- // stray/double colon is an operator typo — reject it so the driver warns + skips
412
- // cleanly rather than posting to the Slack API with a malformed thread_ts.
413
- if (threadTs.includes(":")) {
414
- return undefined;
415
- }
416
- return { channelId, threadTs };
417
- }
418
- // Default wall-clock bound for a webhook run, reclaiming a hung slot. Mirrors
419
- // {@link DEFAULT_CRON_MAX_RUN_MS}; an operator can override via webhook.maxRunMs.
420
- const DEFAULT_WEBHOOK_MAX_RUN_MS = 20 * 60 * 1000;
421
- export function createWebhookChannelDriver(overrides = {}) {
422
- return {
423
- id: "webhook",
424
- label: "Webhook",
425
- async configView(input) {
426
- if (!(await isChannelConfigured(input, WEBHOOK_GATE))) {
427
- return unconfiguredChannelView("webhook", "Webhook");
428
- }
429
- const adapter = await loadWebhookModule();
430
- return await buildChannelConfigView(this, adapter.WEBHOOK_CONFIG_FIELDS, input);
431
- },
432
- configIssues(config) {
433
- return findTriggerOverrideIssues(config.endpoints
434
- .filter((endpoint) => endpoint.enabled)
435
- .map((endpoint) => ({
436
- name: `webhook endpoint "${endpoint.name}"`,
437
- ...(endpoint.model === undefined ? {} : { model: endpoint.model }),
438
- ...(endpoint.effort === undefined ? {} : { effort: endpoint.effort }),
439
- })));
440
- },
441
- async loadConfig(input) {
442
- if (!(await isChannelConfigured(input, WEBHOOK_GATE))) {
443
- return UNCONFIGURED_WEBHOOK_CONFIG;
444
- }
445
- const adapter = await loadWebhookModule();
446
- // cwd is required so `webhook/*.md` endpoint files are discovered.
447
- return await adapter.loadWebhookAdapterConfig({ env: input.env, jsonPath: input.configPath, cwd: input.cwd });
448
- },
449
- isConfigError(error) {
450
- return webhookModule !== undefined && error instanceof webhookModule.WebhookAdapterError;
451
- },
452
- disabledReason(config) {
453
- return config.enabled ? undefined : "Webhook adapter is disabled.";
454
- },
455
- waitingReason(config) {
456
- return config.endpoints.some((endpoint) => endpoint.enabled)
457
- ? undefined
458
- : "Webhook adapter has no enabled endpoints.";
459
- },
460
- async start(input) {
461
- const endpoints = input.config.endpoints.filter((endpoint) => endpoint.enabled);
462
- const endpointByName = new Map(endpoints.map((endpoint) => [endpoint.name, endpoint]));
463
- const inferredNotifyDestination = endpoints.some((endpoint) => endpoint.notify === true && endpoint.notifyConversationId === undefined)
464
- ? await inferUniqueNotifyDestination({
465
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
466
- })
467
- : undefined;
468
- const adapterModule = await loadWebhookModule();
469
- const adapterFactory = overrides.adapterFactory ?? adapterModule.startWebhookAdapter;
470
- const adapter = await adapterFactory({
471
- host: input.config.host,
472
- port: input.config.port,
473
- allowNonLoopback: input.config.allowNonLoopback,
474
- defaultMode: input.config.defaultMode,
475
- retentionMs: input.config.retentionMs,
476
- maxStoredRequests: input.config.maxStoredRequests,
477
- // Default-on max-run bound so a hung (esp. async) webhook can't hold a
478
- // conversation slot forever; mirrors the cron adapter's 20-min default.
479
- maxRunMs: input.config.maxRunMs ?? DEFAULT_WEBHOOK_MAX_RUN_MS,
480
- endpoints: endpoints
481
- .map((endpoint) => ({
482
- name: endpoint.name,
483
- path: endpoint.path,
484
- mode: endpoint.mode,
485
- ...(endpoint.prompt === undefined ? {} : { prompt: endpoint.prompt }),
486
- ...(endpoint.notify === undefined ? {} : { notify: endpoint.notify }),
487
- ...(endpoint.notifyConversationId === undefined ? {} : { notifyConversationId: endpoint.notifyConversationId }),
488
- ...(endpoint.notify === true && endpoint.notifyConversationId === undefined && inferredNotifyDestination !== undefined
489
- ? { notifyFallbackConversationId: inferredNotifyDestination }
490
- : {}),
491
- ...(endpoint.model === undefined ? {} : { model: endpoint.model }),
492
- ...(endpoint.effort === undefined ? {} : { effort: endpoint.effort }),
493
- })),
494
- responder: input.responder,
495
- onResult: (status, request) => {
496
- void deliverNativeWebhookNotification({
497
- endpoint: endpointByName.get(request.metadata.webhook.endpointName),
498
- status,
499
- request,
500
- ...(input.notifyDestination === undefined ? {} : { notifyDestination: input.notifyDestination }),
501
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
502
- ...(input.logger === undefined ? {} : { logger: input.logger }),
503
- });
504
- },
505
- ...(input.logger === undefined ? {} : { logger: input.logger }),
506
- });
507
- return {
508
- summary: {
509
- invokeUrl: adapter.invokeUrl,
510
- port: adapter.port,
511
- invokeUrls: Object.fromEntries((adapter.endpoints ?? []).map((endpoint) => [endpoint.name, endpoint.invokeUrl])),
512
- },
513
- stop: () => adapter.stop(),
514
- };
515
- },
516
- };
517
- }
518
- export function createOpenAIApiChannelDriver(overrides = {}) {
519
- return {
520
- id: "openai-api",
521
- label: "OpenAI API",
522
- async configView(input) {
523
- if (!(await isChannelConfigured(input, OPENAI_API_GATE))) {
524
- return unconfiguredChannelView("openai-api", "OpenAI API");
525
- }
526
- const adapter = await loadOpenAIApiModule();
527
- return await buildChannelConfigView(this, adapter.OPENAI_API_CONFIG_FIELDS, input, { jsonKey: "openaiApi" });
528
- },
529
- async loadConfig(input) {
530
- if (!(await isChannelConfigured(input, OPENAI_API_GATE))) {
531
- return UNCONFIGURED_OPENAI_API_CONFIG;
532
- }
533
- const adapter = await loadOpenAIApiModule();
534
- return await adapter.loadOpenAIApiAdapterConfig({ env: input.env, jsonPath: input.configPath });
535
- },
536
- isConfigError(error) {
537
- return openaiApiModule !== undefined && error instanceof openaiApiModule.OpenAIApiAdapterError;
538
- },
539
- disabledReason(config) {
540
- return config.enabled ? undefined : "OpenAI API adapter is disabled.";
541
- },
542
- async start(input) {
543
- const adapterModule = await loadOpenAIApiModule();
544
- const adapterFactory = overrides.adapterFactory ?? adapterModule.startOpenAIApiAdapter;
545
- const adapter = await adapterFactory({
546
- host: input.config.host,
547
- port: input.config.port,
548
- basePath: input.config.basePath,
549
- allowNonLoopback: input.config.allowNonLoopback,
550
- ...(input.config.apiKey === undefined ? {} : { apiKey: input.config.apiKey }),
551
- modelId: input.config.modelId,
552
- responder: input.responder,
553
- ...(input.logger === undefined ? {} : { logger: input.logger }),
554
- });
555
- const baseUrls = adapter.baseUrls ?? [adapter.baseUrl];
556
- return {
557
- summary: {
558
- baseUrl: adapter.baseUrl,
559
- ...(baseUrls.length > 1 ? { baseUrls } : {}),
560
- },
561
- stop: () => adapter.stop(),
562
- };
563
- },
564
- };
565
- }
566
- /**
567
- * `/v1/info` re-runs local-provider discovery at most this often; the TUI
568
- * fetches info on connect + agent switch (not per turn), so this just bounds
569
- * how often a flaky/slow local endpoint gets probed rather than affecting
570
- * perceived freshness in practice.
571
- */
572
- const LOCAL_MODEL_DISCOVERY_TTL_MS = 30_000;
573
- /**
574
- * The TUI stream endpoint deviates from the channels-off convention: with no
575
- * `tui` section and no `MONO_AGENT_TUI_*` env it is ENABLED on loopback with
576
- * an ephemeral port, so `mono-agent tui` can reach any running agent without a
577
- * per-agent config edit. There is consequently no isChannelConfigured gate and
578
- * no synthetic UNCONFIGURED constant — the real loader always answers (its
579
- * empty-input output is `enabled: true` + defaults). `"tui": {"enabled": false}`
580
- * opts out.
581
- */
582
- export function createTuiChannelDriver(overrides = {}) {
583
- return {
584
- id: "tui",
585
- label: "TUI",
586
- async configView(input) {
587
- const adapter = await loadTuiModule();
588
- return await buildChannelConfigView(this, adapter.TUI_CONFIG_FIELDS, input, { jsonKey: "tui" });
589
- },
590
- async loadConfig(input) {
591
- const adapter = await loadTuiModule();
592
- return await adapter.loadTuiAdapterConfig({ env: input.env, jsonPath: input.configPath });
593
- },
594
- isConfigError(error) {
595
- return tuiModule !== undefined && error instanceof tuiModule.TuiAdapterError;
596
- },
597
- disabledReason(config) {
598
- return config.enabled ? undefined : "TUI stream endpoint is disabled.";
599
- },
600
- async start(input) {
601
- const adapterModule = await loadTuiModule();
602
- const adapterFactory = overrides.adapterFactory ?? adapterModule.startTuiAdapter;
603
- const discoverModels = overrides.discoverModels ?? discoverLocalProviderModels;
604
- const localProviders = input.coreConfig.providers?.local;
605
- // The statically configured candidate models: the primary first, then
606
- // each configured fallback, as canonical `modelReferenceKey` strings,
607
- // de-duplicated (a fallback redundantly naming the primary collapses away).
608
- const configModelKeys = [];
609
- for (const ref of configuredRuntimeModels(input.coreConfig.runtime)) {
610
- const key = modelReferenceKey(ref);
611
- if (!configModelKeys.includes(key)) {
612
- configModelKeys.push(key);
613
- }
614
- }
615
- // Local-provider discovery is async and its result can change after
616
- // startup (an endpoint started later, a model loaded/unloaded) — so
617
- // `info` is a PROVIDER the adapter re-invokes on every GET /v1/info,
618
- // rather than a value snapshotted once at channel composition. A short
619
- // TTL cache keeps a flaky/slow endpoint from being hammered; the TUI
620
- // only calls /v1/info on connect + agent switch, not per turn, so 30s
621
- // of staleness there is unnoticeable in practice.
622
- let discoveryCache;
623
- const discoverModelsCached = async () => {
624
- const now = Date.now();
625
- if (discoveryCache !== undefined && now < discoveryCache.expiresAt) {
626
- return discoveryCache.models;
627
- }
628
- const models = await discoverModels(localProviders);
629
- discoveryCache = { expiresAt: now + LOCAL_MODEL_DISCOVERY_TTL_MS, models };
630
- return models;
631
- };
632
- const buildInfo = async () => {
633
- const discovered = await discoverModelsCached();
634
- const labelByRef = new Map(discovered.map((model) => [model.ref, model.label]));
635
- const models = [...configModelKeys];
636
- for (const model of discovered) {
637
- if (!models.includes(model.ref)) {
638
- models.push(model.ref);
639
- }
640
- }
641
- const modelOptions = {};
642
- for (const ref of models) {
643
- let parsedRef;
644
- try {
645
- parsedRef = parseMonoRuntimeModelReference(ref);
646
- }
647
- catch {
648
- continue;
649
- }
650
- const resolved = resolveModelEffortLevels(parsedRef, localProviders);
651
- const label = labelByRef.get(ref);
652
- const entry = {
653
- ...(resolved.effortLevels === undefined ? {} : { effortLevels: resolved.effortLevels }),
654
- reasoning: resolved.reasoning,
655
- ...(resolved.reasoningMode === undefined ? {} : { reasoningMode: resolved.reasoningMode }),
656
- ...(label === undefined ? {} : { label }),
657
- };
658
- if (Object.keys(entry).length > 0) {
659
- modelOptions[ref] = entry;
660
- }
661
- }
662
- return {
663
- model: modelReferenceKey(input.coreConfig.runtime.model),
664
- ...(input.coreConfig.runtime.effort === undefined ? {} : { effort: input.coreConfig.runtime.effort }),
665
- models,
666
- ...(Object.keys(modelOptions).length === 0 ? {} : { modelOptions }),
667
- };
668
- };
669
- const adapter = await adapterFactory({
670
- host: input.config.host,
671
- port: input.config.port,
672
- basePath: input.config.basePath,
673
- allowNonLoopback: input.config.allowNonLoopback,
674
- ...(input.config.apiKey === undefined ? {} : { apiKey: input.config.apiKey }),
675
- responder: input.responder,
676
- info: buildInfo,
677
- // A dead endpoint must flip the channel to failed, not serve nothing
678
- // silently — the TUI's only discovery signal is this channel's status.
679
- onServerError: (reason) => input.onFailure(reason),
680
- ...(input.logger === undefined ? {} : { logger: input.logger }),
681
- });
682
- return {
683
- summary: { baseUrl: adapter.baseUrl },
684
- stop: () => adapter.stop(),
685
- };
686
- },
687
- };
688
- }
689
- /**
690
- * The live event relay, like the TUI endpoint, deviates from the channels-off
691
- * convention: it is ENABLED by default on loopback with an ephemeral port so
692
- * `mono-agent web` can observe any running agent without a per-agent config edit.
693
- * It is passive and read-only — it relays the host's in-process run-event bus over
694
- * SSE and never drives a turn. `"live": {"enabled": false}` opts out.
695
- */
696
- export function createLiveChannelDriver(overrides = {}) {
697
- return {
698
- id: "live",
699
- label: "Live",
700
- async configView(input) {
701
- const adapter = await loadLiveModule();
702
- return await buildChannelConfigView(this, adapter.LIVE_CONFIG_FIELDS, input, { jsonKey: "live" });
703
- },
704
- async loadConfig(input) {
705
- const adapter = await loadLiveModule();
706
- return await adapter.loadLiveAdapterConfig({ env: input.env, jsonPath: input.configPath });
707
- },
708
- isConfigError(error) {
709
- return liveModule !== undefined && error instanceof liveModule.LiveAdapterError;
710
- },
711
- disabledReason(config) {
712
- return config.enabled ? undefined : "Live event relay is disabled.";
713
- },
714
- async start(input) {
715
- const adapterModule = await loadLiveModule();
716
- const adapterFactory = overrides.adapterFactory ?? adapterModule.startLiveAdapter;
717
- // The host feeds the shared run-event bus (via the broadcast recorder). If it
718
- // has none, still serve an (empty) stream rather than failing the channel.
719
- const bus = input.liveEventBus ?? adapterModule.createLiveEventBus();
720
- const adapter = await adapterFactory({
721
- bus,
722
- host: input.config.host,
723
- port: input.config.port,
724
- basePath: input.config.basePath,
725
- allowNonLoopback: input.config.allowNonLoopback,
726
- ...(input.config.apiKey === undefined ? {} : { apiKey: input.config.apiKey }),
727
- // A dead endpoint must flip the channel to failed, not serve nothing
728
- // silently — the web surface's only per-agent live signal is this status.
729
- onServerError: (reason) => input.onFailure(reason),
730
- ...(input.logger === undefined ? {} : { logger: input.logger }),
731
- });
732
- return {
733
- summary: { baseUrl: adapter.baseUrl },
734
- stop: () => adapter.stop(),
735
- };
736
- },
737
- };
738
- }
739
- /**
740
- * 20 minutes — comfortably above any real briefing/scan (observed max ~6.5 min) yet bounded, so a
741
- * hung run is reclaimed rather than blocking the job indefinitely.
742
- */
743
- const DEFAULT_CRON_MAX_RUN_MS = 20 * 60 * 1000;
744
- const DEFAULT_CRON_FAILURE_NOTICE_COOLDOWN_HOURS = 6;
745
- const MAX_CRON_FAILURE_NOTICE_ERROR_CHARS = 180;
746
- /**
747
- * Whether a notify-enabled turn's final text should suppress delivery: empty /
748
- * whitespace-only (the agent chose to stay silent) or exactly the
749
- * {@link NOTHING_TO_REPORT_SENTINEL} token. Matched trimmed and
750
- * case-insensitively; never substring-matched, so a digest that merely mentions
751
- * the phrase is still delivered.
752
- */
753
- function suppressesNotification(text) {
754
- const trimmed = text?.trim() ?? "";
755
- return trimmed.length === 0 || trimmed.toUpperCase() === NOTHING_TO_REPORT_SENTINEL;
756
- }
757
- /** A conversationId a verbatim notification can be delivered to (a push channel with a notify hook). */
758
- function isDeliverableConversation(conversationId) {
759
- return conversationId.startsWith("telegram:") || conversationId.startsWith("slack:");
760
- }
761
- export function createCronChannelDriver(overrides = {}) {
762
- const failureNoticeLastSentMsByJobId = new Map();
763
- return {
764
- id: "cron",
765
- label: "Cron",
766
- async configView(input) {
767
- if (!(await isChannelConfigured(input, CRON_GATE))) {
768
- return unconfiguredChannelView("cron", "Cron");
769
- }
770
- const adapter = await loadCronModule();
771
- return await buildChannelConfigView(this, adapter.CRON_CONFIG_FIELDS, input);
772
- },
773
- configIssues(config) {
774
- return findTriggerOverrideIssues(config.jobs
775
- .filter((job) => job.enabled)
776
- .map((job) => ({
777
- name: `cron job "${job.id}"`,
778
- ...(job.model === undefined ? {} : { model: job.model }),
779
- ...(job.effort === undefined ? {} : { effort: job.effort }),
780
- })));
781
- },
782
- async loadConfig(input) {
783
- if (!(await isChannelConfigured(input, CRON_GATE))) {
784
- return UNCONFIGURED_CRON_CONFIG;
785
- }
786
- const adapter = await loadCronModule();
787
- return await adapter.loadCronAdapterConfig({ env: input.env, jsonPath: input.configPath, cwd: input.cwd });
788
- },
789
- isConfigError(error) {
790
- return cronModule !== undefined && error instanceof cronModule.CronAdapterError;
791
- },
792
- disabledReason(config) {
793
- const enabledJobs = config.jobs.filter((job) => job.enabled);
794
- return enabledJobs.length > 0 ? undefined : "Cron adapter has no enabled jobs.";
795
- },
796
- async start(input) {
797
- const jobs = input.config.jobs.filter((job) => job.enabled);
798
- const jobById = new Map(jobs.map((job) => [job.id, job]));
799
- const inferredNotifyDestination = jobs.some((job) => job.notify === true && job.notifyConversationId === undefined)
800
- ? await inferUniqueNotifyDestination({
801
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
802
- })
803
- : undefined;
804
- const adapterModule = await loadCronModule();
805
- const adapterFactory = overrides.adapterFactory ?? adapterModule.startCronAdapter;
806
- const adapter = adapterFactory({
807
- responder: input.responder,
808
- // Skip overlapping firings (a job still running when its next tick fires)
809
- // — the legacy/default app behavior. This avoids the scheduler's
810
- // unbounded "queue" default retaining stale ticks in memory when a job
811
- // runs longer than its interval. (Queue/replace remain available to
812
- // programmatic callers of startCronAdapter.) The watchdog below is what
813
- // keeps "skip" safe: a hung run no longer pins the slot forever.
814
- overlap: "skip",
815
- // Reclaim a run whose responder never settles, so a single wedged run can't
816
- // permanently skip every future firing of the job.
817
- maxRunMs: overrides.maxRunMs ?? DEFAULT_CRON_MAX_RUN_MS,
818
- jobs: jobs.map((job) => ({
819
- id: job.id,
820
- expression: job.expression,
821
- timezone: job.timezone,
822
- prompt: job.prompt,
823
- ...(job.conversationId === undefined ? {} : { conversationId: job.conversationId }),
824
- ...(job.maxRunMs === undefined ? {} : { maxRunMs: job.maxRunMs }),
825
- ...(job.notify === undefined ? {} : { notify: job.notify }),
826
- ...(job.notifyConversationId === undefined ? {} : { notifyConversationId: job.notifyConversationId }),
827
- ...(job.notify === true && job.notifyConversationId === undefined && inferredNotifyDestination !== undefined
828
- ? { notifyFallbackConversationId: inferredNotifyDestination }
829
- : {}),
830
- ...(job.model === undefined ? {} : { model: job.model }),
831
- ...(job.effort === undefined ? {} : { effort: job.effort }),
832
- })),
833
- onResult: (result) => {
834
- const level = result.kind === "failed" ? "error" : result.kind === "skipped" ? "warn" : "info";
835
- input.logger?.[level]?.("Cron job finished.", { result });
836
- void deliverCronModelExhaustionFailureNotice({
837
- job: jobById.get(result.jobId),
838
- result,
839
- cooldowns: failureNoticeLastSentMsByJobId,
840
- now: overrides.now ?? (() => new Date()),
841
- ...(input.notifyDestination === undefined ? {} : { notifyDestination: input.notifyDestination }),
842
- ...(input.logger === undefined ? {} : { logger: input.logger }),
843
- });
844
- void deliverNativeCronNotification({
845
- job: jobById.get(result.jobId),
846
- result,
847
- ...(input.notifyDestination === undefined ? {} : { notifyDestination: input.notifyDestination }),
848
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
849
- ...(input.logger === undefined ? {} : { logger: input.logger }),
850
- });
851
- },
852
- ...(input.logger === undefined ? {} : { logger: input.logger }),
853
- });
854
- return {
855
- summary: { jobs: adapter.jobs.length },
856
- async stop() {
857
- adapter.stop();
858
- },
859
- };
860
- },
861
- };
862
- }
863
- async function deliverCronModelExhaustionFailureNotice(input) {
864
- const job = input.job;
865
- if (job?.notify !== true ||
866
- input.result.kind !== "failed" ||
867
- input.result.failureKind !== "provider_unavailable_exhausted") {
868
- return;
869
- }
870
- if (job.notifyConversationId === undefined) {
871
- input.logger?.warn?.("Cron failure notice skipped: notifyConversationId is required.", { jobId: job.id });
872
- return;
873
- }
874
- if (input.notifyDestination === undefined) {
875
- input.logger?.warn?.("Cron failure notice skipped: no delivery hook is available.", { jobId: job.id });
876
- return;
877
- }
878
- const nowMs = input.now().getTime();
879
- const cooldownHours = job.notifyFailureCooldownHours ?? DEFAULT_CRON_FAILURE_NOTICE_COOLDOWN_HOURS;
880
- const cooldownMs = cooldownHours * 60 * 60 * 1000;
881
- const lastSentMs = input.cooldowns.get(job.id);
882
- if (lastSentMs !== undefined && nowMs - lastSentMs < cooldownMs) {
883
- input.logger?.info?.("Cron failure notice skipped: cooldown is active.", {
884
- jobId: job.id,
885
- cooldownHours,
886
- });
887
- return;
888
- }
889
- const destination = job.notifyConversationId;
890
- const text = buildCronModelExhaustionFailureNotice(job, input.result);
891
- try {
892
- const delivery = await input.notifyDestination(destination, text, { verbatim: true });
893
- if (!delivery.delivered) {
894
- input.logger?.warn?.("Cron failure notice was not delivered.", {
895
- jobId: job.id,
896
- conversationId: destination,
897
- ...(delivery.reason === undefined ? {} : { reason: delivery.reason }),
898
- });
899
- return;
900
- }
901
- input.cooldowns.set(job.id, nowMs);
902
- input.logger?.info?.("Cron failure notice delivered.", { jobId: job.id, conversationId: destination });
903
- }
904
- catch (error) {
905
- input.logger?.warn?.("Cron failure notice failed.", {
906
- jobId: job.id,
907
- conversationId: destination,
908
- reason: error instanceof Error ? error.message : String(error),
909
- });
910
- }
911
- }
912
- function buildCronModelExhaustionFailureNotice(job, result) {
913
- const jobId = oneLine(job.id);
914
- const latestError = truncateOneLine(result.error, MAX_CRON_FAILURE_NOTICE_ERROR_CHARS);
915
- const prefix = `Cron job "${jobId}" failed: all configured models failed.`;
916
- return latestError.length === 0 ? prefix : `${prefix} Latest error: ${latestError}`;
917
- }
918
- function truncateOneLine(value, maxChars) {
919
- const collapsed = oneLine(value);
920
- if (collapsed.length <= maxChars) {
921
- return collapsed;
922
- }
923
- return `${collapsed.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
924
- }
925
- function oneLine(value) {
926
- return value.replace(/\s+/gu, " ").trim();
927
- }
928
- async function deliverNativeCronNotification(input) {
929
- const job = input.job;
930
- if (job?.notify !== true || input.result.kind !== "succeeded") {
931
- return;
932
- }
933
- const text = input.result.text;
934
- // The agent stays silent by producing no final answer or the explicit sentinel.
935
- if (text === undefined || suppressesNotification(text)) {
936
- return;
937
- }
938
- try {
939
- if (input.notifyDestination === undefined) {
940
- input.logger?.warn?.("Native cron notification skipped: no delivery hook is available.", { jobId: job.id });
941
- return;
942
- }
943
- const destination = await resolveNativeCronNotifyDestination({
944
- job,
945
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
946
- ...(input.logger === undefined ? {} : { logger: input.logger }),
947
- });
948
- if (destination === undefined) {
949
- return;
950
- }
951
- const delivery = await input.notifyDestination(destination, text, { verbatim: true });
952
- if (!delivery.delivered) {
953
- input.logger?.warn?.("Native cron notification was not delivered.", {
954
- jobId: job.id,
955
- conversationId: destination,
956
- ...(delivery.reason === undefined ? {} : { reason: delivery.reason }),
957
- });
958
- }
959
- }
960
- catch (error) {
961
- input.logger?.warn?.("Native cron notification failed.", {
962
- jobId: job.id,
963
- reason: error instanceof Error ? error.message : String(error),
964
- });
965
- }
966
- }
967
- async function resolveNativeCronNotifyDestination(input) {
968
- if (input.job.notifyConversationId !== undefined) {
969
- return input.job.notifyConversationId;
970
- }
971
- if (input.listNotifyDestinations === undefined) {
972
- input.logger?.warn?.("Native cron notification skipped: no destination is configured and no destination resolver is available.", {
973
- jobId: input.job.id,
974
- });
975
- return undefined;
976
- }
977
- const destinations = await input.listNotifyDestinations();
978
- if (destinations.length !== 1) {
979
- input.logger?.warn?.("Native cron notification skipped: destination inference requires exactly one candidate.", {
980
- jobId: input.job.id,
981
- destinationCount: destinations.length,
982
- });
983
- return undefined;
984
- }
985
- return destinations[0]?.conversationId;
986
- }
987
- async function inferUniqueNotifyDestination(input) {
988
- if (input.listNotifyDestinations === undefined) {
989
- return undefined;
990
- }
991
- const destinations = await input.listNotifyDestinations();
992
- return destinations.length === 1 ? destinations[0]?.conversationId : undefined;
993
- }
994
- async function deliverNativeWebhookNotification(input) {
995
- const endpoint = input.endpoint;
996
- if (endpoint?.notify !== true || input.status.status !== "succeeded") {
997
- return;
998
- }
999
- const text = input.status.text;
1000
- // The agent stays silent by producing no final answer or the explicit sentinel.
1001
- if (text === undefined || suppressesNotification(text)) {
1002
- return;
1003
- }
1004
- const source = { endpointName: input.request.metadata.webhook.endpointName };
1005
- try {
1006
- if (input.notifyDestination === undefined) {
1007
- input.logger?.warn?.("Native webhook notification skipped: no delivery hook is available.", source);
1008
- return;
1009
- }
1010
- const destination = await resolveNativeWebhookNotifyDestination({
1011
- endpoint,
1012
- source,
1013
- requestConversationId: input.request.conversationId,
1014
- ...(input.listNotifyDestinations === undefined ? {} : { listNotifyDestinations: input.listNotifyDestinations }),
1015
- ...(input.logger === undefined ? {} : { logger: input.logger }),
1016
- });
1017
- if (destination === undefined) {
1018
- return;
1019
- }
1020
- const delivery = await input.notifyDestination(destination, text, { verbatim: true });
1021
- if (!delivery.delivered) {
1022
- input.logger?.warn?.("Native webhook notification was not delivered.", {
1023
- ...source,
1024
- conversationId: destination,
1025
- ...(delivery.reason === undefined ? {} : { reason: delivery.reason }),
1026
- });
1027
- }
1028
- }
1029
- catch (error) {
1030
- input.logger?.warn?.("Native webhook notification failed.", {
1031
- ...source,
1032
- reason: error instanceof Error ? error.message : String(error),
1033
- });
1034
- }
1035
- }
1036
- async function resolveNativeWebhookNotifyDestination(input) {
1037
- if (input.endpoint.notifyConversationId !== undefined) {
1038
- return input.endpoint.notifyConversationId;
1039
- }
1040
- // Async-callback pattern: when the inbound payload names a deliverable chat
1041
- // (e.g. the service posts back the originating `telegram:`/`slack:` id), deliver
1042
- // the answer there. The owning channel's allowlist still bounds it.
1043
- if (input.requestConversationId !== undefined && isDeliverableConversation(input.requestConversationId)) {
1044
- return input.requestConversationId;
1045
- }
1046
- if (input.listNotifyDestinations === undefined) {
1047
- input.logger?.warn?.("Native webhook notification skipped: no destination is configured and no destination resolver is available.", input.source);
1048
- return undefined;
1049
- }
1050
- const destinations = await input.listNotifyDestinations();
1051
- if (destinations.length !== 1) {
1052
- input.logger?.warn?.("Native webhook notification skipped: destination inference requires exactly one candidate.", {
1053
- ...input.source,
1054
- destinationCount: destinations.length,
1055
- });
1056
- return undefined;
1057
- }
1058
- return destinations[0]?.conversationId;
1059
- }
1060
- /** Every channel the app can drive, in startup/status display order. */
19
+ export { createCronChannelDriver, createLiveChannelDriver, createOpenAIApiChannelDriver, createSlackChannelDriver, createTelegramChannelDriver, createTuiChannelDriver, createWebhookChannelDriver, slackTargetFromConversation, telegramChatIdFromConversation, };
20
+ /** Every built-in channel driver, in startup/status display order. */
1061
21
  export function defaultChannelDrivers(overrides = {}) {
1062
22
  return [
1063
23
  createTelegramChannelDriver(overrides.telegram),
@@ -1083,127 +43,4 @@ export async function resolveChannelDrivers(input, overrides = {}) {
1083
43
  }
1084
44
  return drivers;
1085
45
  }
1086
- function telegramStartOptions(input, overrides) {
1087
- // Subscribe to (and handle) inline-keyboard taps only when the `TelegramAskButtons`
1088
- // tool is permitted by the tool policy — so enabling the tool is the single
1089
- // switch that turns on the whole ask/answer round-trip. Without it the bot
1090
- // stays message-only and never registers the callback handler.
1091
- const callbacksEnabled = isAdapterSendToolAllowed("TelegramAskButtons", {
1092
- allowedTools: input.coreConfig.tools.allowedTools,
1093
- disallowedTools: input.coreConfig.tools.disallowedTools,
1094
- });
1095
- // Keep host status transitions edge-triggered even if an adapter regression or
1096
- // a stop/restart race repeats a lifecycle callback for the same outage.
1097
- let pollingDegraded = false;
1098
- return {
1099
- botToken: input.config.botToken,
1100
- allowedChatIds: [...input.config.allowedChatIds],
1101
- allowAllChats: input.config.allowAllChats,
1102
- responder: input.responder,
1103
- allowedUpdates: callbacksEnabled ? ["message", "callback_query"] : ["message"],
1104
- ...(callbacksEnabled ? { callbacksEnabled: true } : {}),
1105
- deleteWebhookOnStart: true,
1106
- stream: {
1107
- initialStatusText: "Agent is thinking...",
1108
- editDebounceMs: 350,
1109
- maxSendRetries: 3,
1110
- retryCapMs: 60_000,
1111
- showThoughts: true,
1112
- formatMarkdown: true,
1113
- },
1114
- messages: {
1115
- welcomeText: "Agent is online. Send a message to run the configured runtime.",
1116
- helpText: "Send a message to talk to the agent. Use /cancel to stop an in-flight response.",
1117
- unauthorizedText: "This chat is not allowlisted for this agent.",
1118
- errorText: telegramErrorText,
1119
- },
1120
- // A polling crash is recoverable by construction: the adapter always schedules
1121
- // a backoff restart (it never gives up on its own), so report it as DEGRADED
1122
- // (keep the responder/harness alive) rather than fatal. onPollingRecovered flips
1123
- // the channel back to running once a restarted runner proves a healthy poll.
1124
- onPollingError: (error) => {
1125
- if (pollingDegraded) {
1126
- return;
1127
- }
1128
- pollingDegraded = true;
1129
- input.onDegraded?.(error instanceof Error ? error.message : String(error));
1130
- },
1131
- onPollingRecovered: () => {
1132
- if (!pollingDegraded) {
1133
- return;
1134
- }
1135
- pollingDegraded = false;
1136
- input.onRecovered?.();
1137
- },
1138
- ...(input.config.apiRoot === undefined ? {} : { apiRoot: input.config.apiRoot }),
1139
- ...(telegramAttachmentOptions(input.config) === undefined
1140
- ? {}
1141
- : { attachments: telegramAttachmentOptions(input.config) }),
1142
- ...(input.interaction === undefined
1143
- ? {}
1144
- : {
1145
- pendingAsks: {
1146
- tryResolve: (conversationId, answer, answerKind) => input.interaction.tryResolveAsk(conversationId, answer, answerKind),
1147
- hasPending: (conversationId) => input.interaction.hasPendingAsk?.(conversationId) ?? false,
1148
- cancel: (conversationId) => {
1149
- input.interaction.cancelAsks(conversationId);
1150
- },
1151
- },
1152
- }),
1153
- ...(input.config.ipFamily === undefined ? {} : { transport: { ipFamily: input.config.ipFamily } }),
1154
- ...(input.config.pollWatchdogMs === undefined ? {} : { pollWatchdogMs: input.config.pollWatchdogMs }),
1155
- ...(input.config.commands === undefined ? {} : { commands: [...input.config.commands] }),
1156
- ...(input.config.reactions === undefined ? {} : { reactions: input.config.reactions }),
1157
- ...(input.logger === undefined ? {} : { logger: input.logger }),
1158
- ...(overrides.botFactory === undefined ? {} : { botFactory: overrides.botFactory }),
1159
- ...(overrides.runnerFactory === undefined ? {} : { runnerFactory: overrides.runnerFactory }),
1160
- };
1161
- }
1162
- function telegramErrorText(input) {
1163
- const failure = failureFromUnknown(input.error);
1164
- if (failure?.kind !== undefined) {
1165
- const description = describeRunFailureKind({ failureKind: failure.kind });
1166
- if (description.known || failure.message === undefined || failure.message.trim().length === 0) {
1167
- const explanation = failure.kind === "usage_limit"
1168
- ? usageLimitExplanation(description.explanation, failure.details)
1169
- : description.explanation;
1170
- return `${explanation} ${description.nextStep}`;
1171
- }
1172
- }
1173
- if (failure?.message !== undefined && failure.message.trim().length > 0) {
1174
- return `I could not complete that message: ${failure.message}`;
1175
- }
1176
- return "I could not complete that message. Check the local artifact summary for details.";
1177
- }
1178
- function usageLimitExplanation(explanation, details) {
1179
- const maxTurns = nestedNumber(details, ["diagnostics", "max_turns"]);
1180
- return maxTurns === undefined ? explanation : `${explanation} Configured turn cap: ${maxTurns} turns.`;
1181
- }
1182
- function failureFromUnknown(error) {
1183
- if (!isRecord(error)) {
1184
- return undefined;
1185
- }
1186
- const failure = error.failure;
1187
- if (!isRecord(failure)) {
1188
- return undefined;
1189
- }
1190
- return {
1191
- ...(typeof failure.kind === "string" ? { kind: failure.kind } : {}),
1192
- ...(typeof failure.message === "string" ? { message: failure.message } : {}),
1193
- ...(Object.prototype.hasOwnProperty.call(failure, "details") ? { details: failure.details } : {}),
1194
- };
1195
- }
1196
- function nestedNumber(value, path) {
1197
- let current = value;
1198
- for (const segment of path) {
1199
- if (!isRecord(current)) {
1200
- return undefined;
1201
- }
1202
- current = current[segment];
1203
- }
1204
- return typeof current === "number" && Number.isFinite(current) ? current : undefined;
1205
- }
1206
- function isRecord(value) {
1207
- return typeof value === "object" && value !== null && !Array.isArray(value);
1208
- }
1209
46
  //# sourceMappingURL=channels.js.map