@spacebar_ai/moldclaw-core 2026.3.41 → 2026.3.44

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 (1144) hide show
  1. package/dist/accounts-5qY-dKca.d.ts +103 -0
  2. package/dist/accounts-SqdHz2ZP.js +114 -0
  3. package/dist/acp-cli-E6bcNqiE.js +2093 -0
  4. package/dist/actions.runtime-BU_XMuLk.js +119 -0
  5. package/dist/actions.runtime-CY5h8lqH.js +133 -0
  6. package/dist/agent-scope-lZlwP1At.js +208 -0
  7. package/dist/agents-C4SkadR1.js +853 -0
  8. package/dist/agents-RfwqGCzE.js +222 -0
  9. package/dist/agents.config-CX9CPNfP.js +17 -0
  10. package/dist/agents.config-DF9Zwn9n.js +121 -0
  11. package/dist/allow-list-3WSjz1zl.js +81 -0
  12. package/dist/allowlist-DNbDjFjw.js +142 -0
  13. package/dist/api-BEOpJ7dR.js +117 -0
  14. package/dist/audit-CpJz_eu6.js +787 -0
  15. package/dist/audit-CpfSjvyo.js +54 -0
  16. package/dist/audit-channel.collect.runtime-BeGotloZ.js +605 -0
  17. package/dist/audit-channel.runtime-BJDZ7ETt.js +121 -0
  18. package/dist/audit-extra.async-C2G0mqmk.js +813 -0
  19. package/dist/audit-membership-runtime-B1FqJsPV.js +162 -0
  20. package/dist/audit.deep.runtime-DyL9O_sU.js +25 -0
  21. package/dist/audit.nondeep.runtime-C6jFgJfH.js +832 -0
  22. package/dist/audit.runtime-Dnlsn23e.js +118 -0
  23. package/dist/auth-Ch3Rchm4.js +101 -0
  24. package/dist/auth-choice-CEFSlnLT.js +122 -0
  25. package/dist/auth-choice-CVCef-eU.js +268 -0
  26. package/dist/auth-choice-Cez-pXrg.js +507 -0
  27. package/dist/auth-choice-options-DO78mvPe.js +123 -0
  28. package/dist/auth-choice-prompt-CUkC7Mmb.js +36 -0
  29. package/dist/auth-choice-prompt-DCuQRiVl.js +115 -0
  30. package/dist/auth-choice.apply-helpers-BhbNIV8X.js +66 -0
  31. package/dist/auth-choice.plugin-providers.runtime-4BhqvEw_.js +119 -0
  32. package/dist/auth-profiles-smABVXzp.js +128040 -0
  33. package/dist/auth-profiles.runtime-Cr-ojtTc.js +116 -0
  34. package/dist/banner-CojBHPWr.js +342 -0
  35. package/dist/bluebubbles-BnLsj2Fy.d.ts +6 -0
  36. package/dist/bluebubbles-CVk7M3Bl.js +64 -0
  37. package/dist/bot-DdyrB2z9.d.ts +478 -0
  38. package/dist/brave-w4Fo8WZ3.js +24 -0
  39. package/dist/browser-cli-DWFs3P_i.js +1494 -0
  40. package/dist/build-info.json +3 -3
  41. package/dist/bundled/boot-md/handler.d.ts +1 -1
  42. package/dist/bundled/boot-md/handler.js +35 -35
  43. package/dist/bundled/bootstrap-extra-files/handler.d.ts +1 -1
  44. package/dist/bundled/bootstrap-extra-files/handler.js +1 -1
  45. package/dist/bundled/command-logger/handler.d.ts +1 -1
  46. package/dist/bundled/session-memory/handler.d.ts +1 -1
  47. package/dist/bundled/session-memory/handler.js +36 -36
  48. package/dist/call-Do7wTSr7.js +39 -0
  49. package/dist/call-gdDAt07d.js +640 -0
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/channel-B26pkce0.js +214 -0
  52. package/dist/channel-BJHp0AQC.js +352 -0
  53. package/dist/channel-BKFOv51P.js +4681 -0
  54. package/dist/channel-BNgpOY8v.js +538 -0
  55. package/dist/channel-BcQAAo2P.js +226 -0
  56. package/dist/channel-BvNdnhbx.js +1598 -0
  57. package/dist/channel-C1Rda3Jd.js +306 -0
  58. package/dist/channel-C87DG-F7.js +803 -0
  59. package/dist/channel-CIip0kvZ.js +619 -0
  60. package/dist/channel-CTPxoT_E2.js +316 -0
  61. package/dist/channel-CklaCzUG.js +562 -0
  62. package/dist/channel-CoJnAdLs.js +920 -0
  63. package/dist/channel-D3tafL1_.js +949 -0
  64. package/dist/channel-DFMrP2uu.js +542 -0
  65. package/dist/channel-DMd5cJQe.js +397 -0
  66. package/dist/channel-Dm34kxAJ.js +207 -0
  67. package/dist/channel-DmwF9udn.js +1321 -0
  68. package/dist/channel-account-context-Bjur9nlh.js +103 -0
  69. package/dist/channel-bGnST659.js +943 -0
  70. package/dist/channel-hIgbkTZf.js +575 -0
  71. package/dist/channel-m_TGrDKo.js +497 -0
  72. package/dist/channel-options-DoUPBMa8.js +50 -0
  73. package/dist/channel-plugin-ids-TZIY4hFs.js +26 -0
  74. package/dist/channel-summary-qD54bOBO.js +111 -0
  75. package/dist/channel.runtime-B0H04Dkk.js +199 -0
  76. package/dist/channel.runtime-BU1f3NkV.js +418 -0
  77. package/dist/channel.runtime-Bj1sfLep.js +4011 -0
  78. package/dist/channel.runtime-BtPAAJc3.js +870 -0
  79. package/dist/channel.runtime-Bx-10m_j.js +171 -0
  80. package/dist/channel.runtime-CI_TBywQ.js +179 -0
  81. package/dist/channel.runtime-CSLj14-Z.js +182 -0
  82. package/dist/channel.runtime-D-lTSYAd.js +404 -0
  83. package/dist/channel.runtime-DJqIOSji.js +127 -0
  84. package/dist/channel.runtime-Ec8aQ9V2.js +241 -0
  85. package/dist/channel.runtime-ax5a1jBm.js +218 -0
  86. package/dist/channel.setup-B-ncdYLT.js +9 -0
  87. package/dist/channel.setup-BY4bh5dm.js +9 -0
  88. package/dist/channel.setup-BovsdMnL.js +57 -0
  89. package/dist/channel.setup-CXzXA25h.js +6 -0
  90. package/dist/channel.setup-DcZUEufN.js +8 -0
  91. package/dist/channel.setup-E6zceRsE.js +8 -0
  92. package/dist/channel.setup-Pc7nGbdX.js +11 -0
  93. package/dist/channels/plugins/actions/discord.d.ts +2 -2
  94. package/dist/channels/plugins/actions/discord.js +35 -35
  95. package/dist/channels/plugins/actions/signal.d.ts +1 -1
  96. package/dist/channels/plugins/actions/signal.js +35 -35
  97. package/dist/channels/plugins/actions/telegram.d.ts +2 -2
  98. package/dist/channels/plugins/actions/telegram.js +35 -35
  99. package/dist/channels/plugins/agent-tools/whatsapp-login.d.ts +3 -3
  100. package/dist/channels/plugins/agent-tools/whatsapp-login.js +35 -35
  101. package/dist/channels-CPtE5ND6.js +404 -0
  102. package/dist/channels-Cj8ZolHI.js +1118 -0
  103. package/dist/channels-cli-D2sKrntt.js +291 -0
  104. package/dist/channels-status-issues-CzIHODg2.js +16 -0
  105. package/dist/clawbot-cli-BcwEDmUn.js +118 -0
  106. package/dist/cleanup-utils-D0L17RsX.js +96 -0
  107. package/dist/cli/daemon-cli.js +1 -1
  108. package/dist/cli-BvGVPKnD.js +154 -0
  109. package/dist/command-registry-CADQzTAg.js +14 -0
  110. package/dist/command-registry-ktiJNAJd.js +242 -0
  111. package/dist/command-secret-gateway-CXp10RTM.js +111 -0
  112. package/dist/compact.runtime-DyKL-Iar.js +116 -0
  113. package/dist/completion-cli-Bz4STrpt.js +17 -0
  114. package/dist/completion-cli-pVda2OFb.js +445 -0
  115. package/dist/config-BbvDRSYp.js +31 -0
  116. package/dist/config-CwBv71QC.js +44 -0
  117. package/dist/config-cli-Y0uXHbOw.js +678 -0
  118. package/dist/config-guard-BpW5g7JE.js +118 -0
  119. package/dist/config-validation-B-vLIsbo.js +262 -0
  120. package/dist/config-value-DT3-5958.js +132 -0
  121. package/dist/configure-B9U-jCqP.js +1100 -0
  122. package/dist/configure-BJ3Wrs5b.js +243 -0
  123. package/dist/control-ui-assets-C1YDYi82.js +232 -0
  124. package/dist/control-ui-shared-Dm5Dh0Lo.js +29 -0
  125. package/dist/core-BwKq3krw.js +150 -0
  126. package/dist/core-hjBwfDsW.d.ts +87 -0
  127. package/dist/cron-cli-DTDgfoMh.js +639 -0
  128. package/dist/daemon-cli-C-dkAXR1.js +339 -0
  129. package/dist/daemon-install-Oy0Q5pMF.js +180 -0
  130. package/dist/deliver-DNGnDqF9.js +111 -0
  131. package/dist/deliver-runtime-CCNZIhET.js +111 -0
  132. package/dist/device-id-cli-XvwZbIyC.js +52 -0
  133. package/dist/device-identity-IG5DngWM.js +365 -0
  134. package/dist/devices-cli-DIsxj4xp.js +342 -0
  135. package/dist/diagnostic-DTPopFvh.js +310 -0
  136. package/dist/directory-cli-DTSY3Ktr.js +311 -0
  137. package/dist/directory-config-helpers-DpFcAbmo.d.ts +38 -0
  138. package/dist/directory.static-CBRAUwUW.js +44 -0
  139. package/dist/discord-CrgxhEWw.js +114 -0
  140. package/dist/discovery-DrG7wmAR.js +48 -0
  141. package/dist/dm-policy-shared-DKoGdUpY.d.ts +95 -0
  142. package/dist/dns-cli-BJiz6CLK.js +217 -0
  143. package/dist/docs-cli-Dq2Yi5qO.js +174 -0
  144. package/dist/doctor-completion-D3GeVcFP.js +90 -0
  145. package/dist/doctor-config-flow-B1cMjr8h.js +112 -0
  146. package/dist/doctor-config-flow-BUe7JpV3.js +2437 -0
  147. package/dist/enable-Bc8bCuVe.js +24 -0
  148. package/dist/entry.js +4 -4
  149. package/dist/exec-approvals-cli-kLAev6bP.js +421 -0
  150. package/dist/extensions/acpx/index.d.ts +1 -1
  151. package/dist/extensions/amazon-bedrock/index.d.ts +1 -1
  152. package/dist/extensions/amazon-bedrock/index.js +4 -4
  153. package/dist/extensions/anthropic/index.d.ts +1 -1
  154. package/dist/extensions/anthropic/index.js +35 -35
  155. package/dist/extensions/bluebubbles/index.d.ts +1 -1
  156. package/dist/extensions/bluebubbles/index.js +39 -39
  157. package/dist/extensions/bluebubbles/setup-entry.d.ts +2 -2
  158. package/dist/extensions/bluebubbles/setup-entry.js +39 -39
  159. package/dist/extensions/brave/index.d.ts +1 -1
  160. package/dist/extensions/brave/index.js +5 -5
  161. package/dist/extensions/byteplus/index.d.ts +1 -1
  162. package/dist/extensions/byteplus/index.js +35 -35
  163. package/dist/extensions/cloudflare-ai-gateway/index.d.ts +1 -1
  164. package/dist/extensions/cloudflare-ai-gateway/index.js +36 -36
  165. package/dist/extensions/copilot-proxy/index.d.ts +1 -1
  166. package/dist/extensions/copilot-proxy/index.js +4 -4
  167. package/dist/extensions/device-pair/index.d.ts +1 -1
  168. package/dist/extensions/device-pair/index.js +4 -4
  169. package/dist/extensions/diagnostics-otel/index.d.ts +1 -1
  170. package/dist/extensions/diagnostics-otel/index.js +4 -4
  171. package/dist/extensions/diffs/index.d.ts +1 -1
  172. package/dist/extensions/discord/index.d.ts +1 -1
  173. package/dist/extensions/discord/index.js +40 -40
  174. package/dist/extensions/discord/setup-entry.d.ts +1 -1
  175. package/dist/extensions/discord/setup-entry.js +38 -38
  176. package/dist/extensions/elevenlabs/index.d.ts +1 -1
  177. package/dist/extensions/elevenlabs/index.js +35 -35
  178. package/dist/extensions/feishu/index.d.ts +2 -2
  179. package/dist/extensions/feishu/index.js +40 -40
  180. package/dist/extensions/feishu/setup-entry.d.ts +2 -2
  181. package/dist/extensions/feishu/setup-entry.js +37 -37
  182. package/dist/extensions/firecrawl/index.d.ts +1 -1
  183. package/dist/extensions/firecrawl/index.js +35 -35
  184. package/dist/extensions/github-copilot/index.d.ts +1 -1
  185. package/dist/extensions/github-copilot/index.js +35 -35
  186. package/dist/extensions/google/index.d.ts +1 -1
  187. package/dist/extensions/google/index.js +35 -35
  188. package/dist/extensions/googlechat/index.d.ts +1 -1
  189. package/dist/extensions/googlechat/index.js +38 -38
  190. package/dist/extensions/googlechat/setup-entry.d.ts +1 -1
  191. package/dist/extensions/googlechat/setup-entry.js +38 -38
  192. package/dist/extensions/huggingface/index.d.ts +1 -1
  193. package/dist/extensions/huggingface/index.js +35 -35
  194. package/dist/extensions/imessage/index.d.ts +1 -1
  195. package/dist/extensions/imessage/index.js +39 -39
  196. package/dist/extensions/imessage/setup-entry.d.ts +1 -1
  197. package/dist/extensions/imessage/setup-entry.js +39 -39
  198. package/dist/extensions/irc/index.d.ts +1 -1
  199. package/dist/extensions/irc/index.js +38 -38
  200. package/dist/extensions/irc/setup-entry.d.ts +2 -2
  201. package/dist/extensions/irc/setup-entry.js +38 -38
  202. package/dist/extensions/kakao-talkchannel/index.d.ts +1 -1
  203. package/dist/extensions/kakao-talkchannel/index.js +4 -4
  204. package/dist/extensions/kilocode/index.d.ts +1 -1
  205. package/dist/extensions/kilocode/index.js +35 -35
  206. package/dist/extensions/kimi-coding/index.d.ts +1 -1
  207. package/dist/extensions/kimi-coding/index.js +35 -35
  208. package/dist/extensions/line/index.d.ts +1 -1
  209. package/dist/extensions/line/index.js +37 -37
  210. package/dist/extensions/line/setup-entry.d.ts +1 -1
  211. package/dist/extensions/line/setup-entry.js +37 -37
  212. package/dist/extensions/llm-task/index.d.ts +1 -1
  213. package/dist/extensions/llm-task/index.js +35 -35
  214. package/dist/extensions/lobster/index.d.ts +1 -1
  215. package/dist/extensions/lobster/index.js +4 -4
  216. package/dist/extensions/matrix/index.d.ts +1 -1
  217. package/dist/extensions/matrix/index.js +40 -40
  218. package/dist/extensions/matrix/setup-entry.d.ts +2 -2
  219. package/dist/extensions/matrix/setup-entry.js +40 -40
  220. package/dist/extensions/mattermost/index.d.ts +1 -1
  221. package/dist/extensions/mattermost/index.js +37 -37
  222. package/dist/extensions/mattermost/setup-entry.d.ts +2 -2
  223. package/dist/extensions/mattermost/setup-entry.js +37 -37
  224. package/dist/extensions/memory-core/index.d.ts +1 -1
  225. package/dist/extensions/memory-core/index.js +4 -4
  226. package/dist/extensions/memory-lancedb/index.d.ts +1 -1
  227. package/dist/extensions/memory-lancedb/index.js +4 -4
  228. package/dist/extensions/microsoft/index.d.ts +1 -1
  229. package/dist/extensions/microsoft/index.js +35 -35
  230. package/dist/extensions/minimax/index.d.ts +1 -1
  231. package/dist/extensions/minimax/index.js +35 -35
  232. package/dist/extensions/mistral/index.d.ts +1 -1
  233. package/dist/extensions/mistral/index.js +35 -35
  234. package/dist/extensions/modelstudio/index.d.ts +1 -1
  235. package/dist/extensions/modelstudio/index.js +35 -35
  236. package/dist/extensions/moonshot/index.d.ts +1 -1
  237. package/dist/extensions/moonshot/index.js +35 -35
  238. package/dist/extensions/msteams/index.d.ts +1 -1
  239. package/dist/extensions/msteams/index.js +40 -40
  240. package/dist/extensions/msteams/setup-entry.d.ts +1 -1
  241. package/dist/extensions/msteams/setup-entry.js +40 -40
  242. package/dist/extensions/nextcloud-talk/index.d.ts +1 -1
  243. package/dist/extensions/nextcloud-talk/index.js +37 -37
  244. package/dist/extensions/nextcloud-talk/setup-entry.d.ts +2 -2
  245. package/dist/extensions/nextcloud-talk/setup-entry.js +37 -37
  246. package/dist/extensions/nostr/index.d.ts +1 -1
  247. package/dist/extensions/nostr/index.js +37 -37
  248. package/dist/extensions/nostr/setup-entry.d.ts +1 -1
  249. package/dist/extensions/nostr/setup-entry.js +37 -37
  250. package/dist/extensions/nvidia/index.d.ts +1 -1
  251. package/dist/extensions/nvidia/index.js +4 -4
  252. package/dist/extensions/ollama/index.d.ts +1 -1
  253. package/dist/extensions/ollama/index.js +7 -7
  254. package/dist/extensions/open-prose/index.d.ts +1 -1
  255. package/dist/extensions/open-prose/index.js +4 -4
  256. package/dist/extensions/openai/index.d.ts +1 -1
  257. package/dist/extensions/openai/index.js +35 -35
  258. package/dist/extensions/opencode/index.d.ts +1 -1
  259. package/dist/extensions/opencode/index.js +35 -35
  260. package/dist/extensions/opencode-go/index.d.ts +1 -1
  261. package/dist/extensions/opencode-go/index.js +35 -35
  262. package/dist/extensions/openrouter/index.d.ts +1 -1
  263. package/dist/extensions/openrouter/index.js +35 -35
  264. package/dist/extensions/openshell/index.d.ts +1 -1
  265. package/dist/extensions/openshell/index.js +35 -35
  266. package/dist/extensions/perplexity/index.d.ts +1 -1
  267. package/dist/extensions/perplexity/index.js +5 -5
  268. package/dist/extensions/phone-control/index.d.ts +1 -1
  269. package/dist/extensions/phone-control/index.js +4 -4
  270. package/dist/extensions/qianfan/index.d.ts +1 -1
  271. package/dist/extensions/qianfan/index.js +35 -35
  272. package/dist/extensions/qwen-portal-auth/index.d.ts +1 -1
  273. package/dist/extensions/qwen-portal-auth/index.js +35 -35
  274. package/dist/extensions/sglang/index.d.ts +1 -1
  275. package/dist/extensions/sglang/index.js +35 -35
  276. package/dist/extensions/signal/index.d.ts +1 -1
  277. package/dist/extensions/signal/index.js +38 -38
  278. package/dist/extensions/signal/setup-entry.d.ts +1 -1
  279. package/dist/extensions/signal/setup-entry.js +38 -38
  280. package/dist/extensions/slack/index.d.ts +1 -1
  281. package/dist/extensions/slack/index.js +39 -39
  282. package/dist/extensions/slack/setup-entry.d.ts +1 -1
  283. package/dist/extensions/slack/setup-entry.js +38 -38
  284. package/dist/extensions/synology-chat/index.d.ts +1 -1
  285. package/dist/extensions/synology-chat/index.js +37 -37
  286. package/dist/extensions/synology-chat/setup-entry.d.ts +1 -1
  287. package/dist/extensions/synology-chat/setup-entry.js +37 -37
  288. package/dist/extensions/synthetic/index.d.ts +1 -1
  289. package/dist/extensions/synthetic/index.js +35 -35
  290. package/dist/extensions/talk-voice/index.d.ts +1 -1
  291. package/dist/extensions/talk-voice/index.js +35 -35
  292. package/dist/extensions/telegram/index.d.ts +1 -1
  293. package/dist/extensions/telegram/index.js +38 -38
  294. package/dist/extensions/telegram/setup-entry.d.ts +1 -1
  295. package/dist/extensions/telegram/setup-entry.js +37 -37
  296. package/dist/extensions/thread-ownership/index.d.ts +1 -1
  297. package/dist/extensions/thread-ownership/index.js +4 -4
  298. package/dist/extensions/tlon/index.d.ts +1 -1
  299. package/dist/extensions/tlon/index.js +37 -37
  300. package/dist/extensions/tlon/setup-entry.d.ts +1 -1
  301. package/dist/extensions/tlon/setup-entry.js +37 -37
  302. package/dist/extensions/together/index.d.ts +1 -1
  303. package/dist/extensions/together/index.js +35 -35
  304. package/dist/extensions/twitch/index.d.ts +2 -2
  305. package/dist/extensions/twitch/index.js +37 -37
  306. package/dist/extensions/venice/index.d.ts +1 -1
  307. package/dist/extensions/venice/index.js +35 -35
  308. package/dist/extensions/vercel-ai-gateway/index.d.ts +1 -1
  309. package/dist/extensions/vercel-ai-gateway/index.js +36 -36
  310. package/dist/extensions/vllm/index.d.ts +1 -1
  311. package/dist/extensions/vllm/index.js +35 -35
  312. package/dist/extensions/voice-call/index.d.ts +1 -1
  313. package/dist/extensions/voice-call/index.js +35 -35
  314. package/dist/extensions/volcengine/index.d.ts +1 -1
  315. package/dist/extensions/volcengine/index.js +35 -35
  316. package/dist/extensions/whatsapp/index.d.ts +1 -1
  317. package/dist/extensions/whatsapp/index.js +38 -38
  318. package/dist/extensions/whatsapp/setup-entry.d.ts +1 -1
  319. package/dist/extensions/whatsapp/setup-entry.js +38 -38
  320. package/dist/extensions/xai/index.d.ts +1 -1
  321. package/dist/extensions/xai/index.js +35 -35
  322. package/dist/extensions/xiaomi/index.d.ts +1 -1
  323. package/dist/extensions/xiaomi/index.js +35 -35
  324. package/dist/extensions/zai/index.d.ts +1 -1
  325. package/dist/extensions/zai/index.js +35 -35
  326. package/dist/extensions/zalo/index.d.ts +1 -1
  327. package/dist/extensions/zalo/index.js +39 -39
  328. package/dist/extensions/zalo/setup-entry.d.ts +1 -1
  329. package/dist/extensions/zalo/setup-entry.js +39 -39
  330. package/dist/extensions/zalouser/index.d.ts +1 -1
  331. package/dist/extensions/zalouser/index.js +40 -40
  332. package/dist/extensions/zalouser/setup-entry.d.ts +1 -1
  333. package/dist/extensions/zalouser/setup-entry.js +40 -40
  334. package/dist/feishu-fIcnHDTd.d.ts +36 -0
  335. package/dist/gateway-cli-0c-8h93_.js +26437 -0
  336. package/dist/gateway-install-token-1PwJvrBY.js +163 -0
  337. package/dist/gateway-rpc-C0Vk51W7.js +26 -0
  338. package/dist/gateway-runtime-CBm3CCoA.js +69 -0
  339. package/dist/git-commit-BTWXFY41.js +177 -0
  340. package/dist/git-commit-D6GTN5Yt.js +2 -0
  341. package/dist/googlechat-BQr4xgoZ.js +307 -0
  342. package/dist/googlechat-BvwsCVKl.d.ts +12 -0
  343. package/dist/group-access-DpiQnd-G.d.ts +61 -0
  344. package/dist/health-6yZQGADY.js +113 -0
  345. package/dist/health-C9DYGyRe.js +570 -0
  346. package/dist/heartbeat-summary-Dct2lqJj.js +57 -0
  347. package/dist/help-CtwSApfq.js +81 -0
  348. package/dist/hooks-9gokOxZ5.d.ts +6 -0
  349. package/dist/hooks-cli-BegKzHZT.js +1000 -0
  350. package/dist/hooks-status-Bm_pGORf.js +78 -0
  351. package/dist/http-registry-D-S6a1Na.d.ts +20 -0
  352. package/dist/identity-file-Diub2a0t.js +60 -0
  353. package/dist/image-generation-CbIVzmAR.d.ts +9 -0
  354. package/dist/imessage-Bgok9kfl.js +31 -0
  355. package/dist/imessage-VIHePprL.js +115 -0
  356. package/dist/inbound-reply-dispatch-B53GAGWq.js +71 -0
  357. package/dist/inbound-reply-dispatch-n7U3qg15.d.ts +72 -0
  358. package/dist/index.js +2 -2
  359. package/dist/install-target-oz1pjfHH.js +574 -0
  360. package/dist/installs-CUFm5V8a.js +532 -0
  361. package/dist/io-BaBxjB1v.js +9739 -0
  362. package/dist/io-CgHb1Jld.js +29 -0
  363. package/dist/irc-CaRKzGvW.js +672 -0
  364. package/dist/library-C5SNBCMb.js +112 -0
  365. package/dist/lifecycle-core-Dn8PK6nk.js +382 -0
  366. package/dist/line/accounts.d.ts +2 -2
  367. package/dist/line/send.d.ts +1 -1
  368. package/dist/line/send.js +7 -7
  369. package/dist/line/template-messages.d.ts +1 -1
  370. package/dist/line-B5QFpgN_.d.ts +75 -0
  371. package/dist/line-fePrrQOD.js +530 -0
  372. package/dist/llm-slug-generator-hKae3XDA.js +67 -0
  373. package/dist/llm-slug-generator.d.ts +1 -1
  374. package/dist/llm-slug-generator.js +36 -36
  375. package/dist/logging-CdisccbY.js +13 -0
  376. package/dist/logging-LKQSgX1d.js +30 -0
  377. package/dist/login-qr-C1YWh4nE.js +233 -0
  378. package/dist/login-qr-WFluMDMb.js +112 -0
  379. package/dist/logs-cli-CNzOvZ2d.js +256 -0
  380. package/dist/manager-runtime-DgMhLTkR.js +111 -0
  381. package/dist/manager.runtime-hUWgpPt2.js +715 -0
  382. package/dist/manifest-registry-CS_p1OBQ.js +1329 -0
  383. package/dist/matrix-43_RGLZN.d.ts +68 -0
  384. package/dist/matrix-CCFxHfxa.js +1269 -0
  385. package/dist/matrix-DWs_qIkJ.js +1495 -0
  386. package/dist/mcp-cli-Ci2jvv3s.js +87 -0
  387. package/dist/media-understanding.runtime-Cdr6iTW6.js +116 -0
  388. package/dist/memory-cli-LZbyF0Iu.js +111 -0
  389. package/dist/memory-search-BHhETk6u.js +17 -0
  390. package/dist/memory-search-tTD5o_rU.js +204 -0
  391. package/dist/method-scopes-B2ZKSsxQ.js +2452 -0
  392. package/dist/model-auth-markers-LqZ4qhrZ.d.ts +20 -0
  393. package/dist/model-picker-CTR5mo4v.js +112 -0
  394. package/dist/model-picker-DG4z_dBs.js +390 -0
  395. package/dist/model-picker.runtime-DMQ9Pj9_.js +125 -0
  396. package/dist/model-selection-bBBxfXdb.js +653 -0
  397. package/dist/model-suppression.runtime-BVG75tZ7.js +116 -0
  398. package/dist/models-BjkVLfgw.js +2514 -0
  399. package/dist/models-ZO01Q4cx.js +118 -0
  400. package/dist/models-cli-DemdF-bm.js +309 -0
  401. package/dist/models-config-B2Jja8ua.js +111 -0
  402. package/dist/models-config.providers.discovery-puxTsH39.d.ts +18 -0
  403. package/dist/moldclaw-root-Cb6HRlUO.js +92 -0
  404. package/dist/monitor-BP4idxJD.js +782 -0
  405. package/dist/monitor-B_eP8Eim.js +772 -0
  406. package/dist/monitor-CRHYNl5J.js +3468 -0
  407. package/dist/monitor-Ci1Xg4g3.js +113 -0
  408. package/dist/monitor-DEodDl3z.js +6823 -0
  409. package/dist/monitor-DJlNKuMz.js +115 -0
  410. package/dist/monitor-DvFwDS9w.js +3076 -0
  411. package/dist/monitor-shared--cEjSf8s.js +444 -0
  412. package/dist/msteams-CV2a8uE8.js +852 -0
  413. package/dist/node-cli-Of2g7DSd.js +2503 -0
  414. package/dist/node-resolve-BYC2FbO2.js +835 -0
  415. package/dist/nodes-cli-CPHM6Upj.js +1380 -0
  416. package/dist/nostr-BFKRoOlz.d.ts +7 -0
  417. package/dist/nostr-lHpcBzz4.js +8744 -0
  418. package/dist/npm-resolution-kqHN85wB.js +60 -0
  419. package/dist/oauth-env-CLG8KOrz.js +10 -0
  420. package/dist/onboard-BON0C360.js +48 -0
  421. package/dist/onboard-CRkIBgOI.js +589 -0
  422. package/dist/onboard-DsKI17iq.js +25 -0
  423. package/dist/onboard-channels-BY3IbBBf.js +1241 -0
  424. package/dist/onboard-channels-CLKdRxvW.js +205 -0
  425. package/dist/onboard-custom-BjPrMo_R.js +571 -0
  426. package/dist/onboard-custom-DqcPiZBN.js +114 -0
  427. package/dist/onboard-helpers-BkrOY5OE.js +113 -0
  428. package/dist/onboard-helpers-DiSRTpZC.js +335 -0
  429. package/dist/onboard-hooks-pzEPZAvl.js +72 -0
  430. package/dist/onboard-remote-ChyLC6Dk.js +181 -0
  431. package/dist/onboard-remote-DHmK9ntl.js +117 -0
  432. package/dist/onboard-search-BgA3jEMW.js +302 -0
  433. package/dist/onboard-skills-BMo_NvnW.js +133 -0
  434. package/dist/onboard-skills-Bba-Z2p8.js +117 -0
  435. package/dist/outbound-media-BHD4aJEX.d.ts +11 -0
  436. package/dist/outbound-media-DSno0N82.js +11 -0
  437. package/dist/pairing-access-CzHpaM0R.d.ts +21 -0
  438. package/dist/pairing-cli-CmklqK0q.js +217 -0
  439. package/dist/perplexity-CXwMDD3u.js +24 -0
  440. package/dist/persistent-dedupe-B9vrAf8t.d.ts +26 -0
  441. package/dist/pi-model-discovery-runtime-BrK7tcaO.js +111 -0
  442. package/dist/pi-tools.before-tool-call.runtime-C5yLUogH.js +381 -0
  443. package/dist/plugin-install-C4AWJIFP.js +117 -0
  444. package/dist/plugin-install-CB3J1hfV.js +184 -0
  445. package/dist/plugin-install-plan-7itZiegi.js +49 -0
  446. package/dist/plugin-registry-DX_GFoiz.js +113 -0
  447. package/dist/plugin-registry-e3cxTtvb.js +49 -0
  448. package/dist/plugin-sdk/account-resolution.js +35 -35
  449. package/dist/plugin-sdk/acp-runtime.js +35 -35
  450. package/dist/plugin-sdk/agent-runtime.js +35 -35
  451. package/dist/plugin-sdk/bluebubbles.js +37 -37
  452. package/dist/plugin-sdk/channel-config-helpers.js +35 -35
  453. package/dist/plugin-sdk/channel-policy.js +35 -35
  454. package/dist/plugin-sdk/channel-runtime.js +35 -35
  455. package/dist/plugin-sdk/compat.js +36 -36
  456. package/dist/plugin-sdk/config-runtime.js +35 -35
  457. package/dist/plugin-sdk/conversation-runtime.js +35 -35
  458. package/dist/plugin-sdk/copilot-proxy.js +4 -4
  459. package/dist/plugin-sdk/core.js +4 -4
  460. package/dist/plugin-sdk/device-pair.js +4 -4
  461. package/dist/plugin-sdk/discord.js +35 -35
  462. package/dist/plugin-sdk/feishu.js +35 -35
  463. package/dist/plugin-sdk/gateway-runtime.js +10 -10
  464. package/dist/plugin-sdk/googlechat.js +37 -37
  465. package/dist/plugin-sdk/image-generation-runtime.js +35 -35
  466. package/dist/plugin-sdk/image-generation.js +35 -35
  467. package/dist/plugin-sdk/imessage.js +36 -36
  468. package/dist/plugin-sdk/index.js +35 -35
  469. package/dist/plugin-sdk/infra-runtime.js +35 -35
  470. package/dist/plugin-sdk/irc.js +37 -37
  471. package/dist/plugin-sdk/line.js +36 -36
  472. package/dist/plugin-sdk/llm-task.js +35 -35
  473. package/dist/plugin-sdk/lobster.js +4 -4
  474. package/dist/plugin-sdk/matrix.js +37 -37
  475. package/dist/plugin-sdk/mattermost.js +36 -36
  476. package/dist/plugin-sdk/media-runtime.js +35 -35
  477. package/dist/plugin-sdk/media-understanding-runtime.js +35 -35
  478. package/dist/plugin-sdk/media-understanding.js +35 -35
  479. package/dist/plugin-sdk/memory-lancedb.js +4 -4
  480. package/dist/plugin-sdk/minimax-portal-auth.js +4 -4
  481. package/dist/plugin-sdk/msteams.js +38 -38
  482. package/dist/plugin-sdk/nextcloud-talk.js +36 -36
  483. package/dist/plugin-sdk/nostr.js +36 -36
  484. package/dist/plugin-sdk/ollama-setup.js +9 -9
  485. package/dist/plugin-sdk/open-prose.js +4 -4
  486. package/dist/plugin-sdk/phone-control.js +4 -4
  487. package/dist/plugin-sdk/plugin-runtime.js +35 -35
  488. package/dist/plugin-sdk/provider-auth.js +35 -35
  489. package/dist/plugin-sdk/provider-models.js +5 -5
  490. package/dist/plugin-sdk/provider-onboard.js +4 -4
  491. package/dist/plugin-sdk/provider-setup.js +39 -39
  492. package/dist/plugin-sdk/provider-stream.js +4 -4
  493. package/dist/plugin-sdk/provider-usage.js +4 -4
  494. package/dist/plugin-sdk/qwen-portal-auth.js +35 -35
  495. package/dist/plugin-sdk/reply-history.js +35 -35
  496. package/dist/plugin-sdk/reply-runtime.js +35 -35
  497. package/dist/plugin-sdk/routing.js +3 -3
  498. package/dist/plugin-sdk/sandbox.js +35 -35
  499. package/dist/plugin-sdk/security-runtime.js +35 -35
  500. package/dist/plugin-sdk/self-hosted-provider-setup.js +37 -37
  501. package/dist/plugin-sdk/setup.js +35 -35
  502. package/dist/plugin-sdk/signal.js +35 -35
  503. package/dist/plugin-sdk/slack.js +35 -35
  504. package/dist/plugin-sdk/speech-runtime.js +35 -35
  505. package/dist/plugin-sdk/speech.js +35 -35
  506. package/dist/plugin-sdk/src/secrets/secure-file-store.d.ts +26 -0
  507. package/dist/plugin-sdk/src/subscription/provider.d.ts +5 -3
  508. package/dist/plugin-sdk/synology-chat.js +36 -36
  509. package/dist/plugin-sdk/talk-voice.js +4 -4
  510. package/dist/plugin-sdk/telegram.js +35 -35
  511. package/dist/plugin-sdk/text-runtime.js +7 -7
  512. package/dist/plugin-sdk/thread-ownership.js +4 -4
  513. package/dist/plugin-sdk/tlon.js +36 -36
  514. package/dist/plugin-sdk/twitch.js +35 -35
  515. package/dist/plugin-sdk/voice-call.js +35 -35
  516. package/dist/plugin-sdk/whatsapp.js +35 -35
  517. package/dist/plugin-sdk/zalo.js +38 -38
  518. package/dist/plugin-sdk/zalouser.js +38 -38
  519. package/dist/plugins/runtime/index.d.ts +1 -1
  520. package/dist/plugins/runtime/index.js +35 -35
  521. package/dist/plugins-DF5FaTO0.js +111 -0
  522. package/dist/plugins-cli-CvTJemqC.js +917 -0
  523. package/dist/policy-CNXISK_a.js +143 -0
  524. package/dist/preflight-audio.runtime-RP000oxo.js +116 -0
  525. package/dist/probe-BkM5pykD.js +21 -0
  526. package/dist/probe-DKbRTJv5.js +1793 -0
  527. package/dist/probe-DkrfRsjU.js +47 -0
  528. package/dist/probe-DpcJ0WeP.js +129 -0
  529. package/dist/probe-auth-BcNjX8hy.js +40 -0
  530. package/dist/probe-auth-DhuAb8ls.js +48 -0
  531. package/dist/probe-wciBj-aL.js +6329 -0
  532. package/dist/program-C8-p0mW5.js +253 -0
  533. package/dist/prompt-select-styled-DH0pVoc0.js +2673 -0
  534. package/dist/provider-api-key-auth.runtime-CAFeIQ1u.js +121 -0
  535. package/dist/provider-auth-choice-CB_HzdTl.js +126 -0
  536. package/dist/provider-auth-choice-helpers-hzDkh3f1.js +48 -0
  537. package/dist/provider-auth-choice-preference-BHCXvNSE.js +189 -0
  538. package/dist/provider-auth-choice.runtime-Dx4ms2C5.js +123 -0
  539. package/dist/provider-auth-choices-0KaDNPBQ.js +57 -0
  540. package/dist/provider-auth-guidance-BaAUiNr_.js +34 -0
  541. package/dist/provider-auth-result-Bto1bYtS.d.ts +18 -0
  542. package/dist/provider-models-DxOmeToO.d.ts +867 -0
  543. package/dist/provider-models-xnyxy6mO.js +2113 -0
  544. package/dist/provider-ollama-setup-DBYK__ov.d.ts +32 -0
  545. package/dist/provider-ollama-setup-QzgCxj44.js +314 -0
  546. package/dist/provider-onboard-B9ionepI.js +139 -0
  547. package/dist/provider-onboard-CURxJ_UX.d.ts +40 -0
  548. package/dist/provider-runtime.runtime-4xwmsl5L.js +111 -0
  549. package/dist/provider-self-hosted-setup-BHd24EDG.js +182 -0
  550. package/dist/provider-self-hosted-setup-qeY8BYSy.d.ts +61 -0
  551. package/dist/provider-stream-Chz_EFw3.js +512 -0
  552. package/dist/provider-usage-C11Q7UwS.js +111 -0
  553. package/dist/provider-usage-kxemdMp2.js +633 -0
  554. package/dist/provider-wizard-CanJoxNC.js +152 -0
  555. package/dist/push-apns-Dsajnm8C.js +1038 -0
  556. package/dist/pw-ai-DUe4BbH2.js +1867 -0
  557. package/dist/qmd-manager-CAAFp7qK.js +1570 -0
  558. package/dist/qr-cli-Bu2jqTPY.js +113 -0
  559. package/dist/qr-cli-Bu9Z-X48.js +369 -0
  560. package/dist/reactions-Cpfum4iU.js +281 -0
  561. package/dist/read-only-account-inspect.discord.runtime-BK0LaMgC.js +116 -0
  562. package/dist/read-only-account-inspect.slack.runtime-DgKiC5wT.js +116 -0
  563. package/dist/read-only-account-inspect.telegram.runtime-mxfgFVOU.js +116 -0
  564. package/dist/redact-snapshot-DD8A4tdd.js +2663 -0
  565. package/dist/register.agent-DU4FtrU2.js +439 -0
  566. package/dist/register.backup-8nOYtJqg.js +625 -0
  567. package/dist/register.configure-DmtecqIH.js +252 -0
  568. package/dist/register.maintenance-Dir3ulKP.js +574 -0
  569. package/dist/register.message-Cfl-f3Ju.js +709 -0
  570. package/dist/register.onboard-Bv7WVzEi.js +192 -0
  571. package/dist/register.setup-BIyeI8RY.js +212 -0
  572. package/dist/register.status-health-sessions-C69WQcF4.js +498 -0
  573. package/dist/register.subclis-B_4KCgTd.js +315 -0
  574. package/dist/register.subclis-BeXsmgBL.js +13 -0
  575. package/dist/replies-DdcFUmki.js +110 -0
  576. package/dist/resolve-channels-DRZqPV5o.js +226 -0
  577. package/dist/resolve-channels-DxW1kqxA.js +262 -0
  578. package/dist/resolve-route-DdX-HBVt.js +538 -0
  579. package/dist/resolve-users-rgCQvkLs.js +143 -0
  580. package/dist/root-help-QAkoA7GD.js +32 -0
  581. package/dist/routes-CcJNnwTF.js +7097 -0
  582. package/dist/rpc-DDUAlBbH.js +67 -0
  583. package/dist/run-main-D9ci5pn7.js +424 -0
  584. package/dist/runtime-Bitmi8Er.d.ts +26 -0
  585. package/dist/runtime-discord-ops.runtime-T4sf7aRB.js +9078 -0
  586. package/dist/runtime-slack-ops.runtime-BQpP48mC.js +4556 -0
  587. package/dist/runtime-telegram-ops.runtime-cVO5dqOp.js +133 -0
  588. package/dist/runtime-whatsapp-login.runtime-DtNx0dSY.js +114 -0
  589. package/dist/runtime-whatsapp-outbound.runtime-Bw47QbFK.js +117 -0
  590. package/dist/sandbox-cli-DsFwjbjC.js +535 -0
  591. package/dist/search-manager-BRAK8fEe.js +16 -0
  592. package/dist/search-manager-BS5Db0A6.js +386 -0
  593. package/dist/secrets-cli-D3J46TJp.js +2070 -0
  594. package/dist/security-cli-B866M9cB.js +575 -0
  595. package/dist/send-B1pX9_Oc.js +283 -0
  596. package/dist/send-B2RrLg83.js +100 -0
  597. package/dist/send-DFnV__Aq.js +1025 -0
  598. package/dist/send-DZIH6CJt.js +629 -0
  599. package/dist/send-sl9WnKbW.js +631 -0
  600. package/dist/server-node-events-BT6egg20.js +506 -0
  601. package/dist/server-zI_K-D05.js +107 -0
  602. package/dist/sessions-C8kiAcoJ.js +112 -0
  603. package/dist/sessions-DLBpp52_.js +218 -0
  604. package/dist/setup-C7eOzMiC.js +387 -0
  605. package/dist/setup-CFIMq-Pz.d.ts +37 -0
  606. package/dist/setup-binary-CcAv8NXz.js +406 -0
  607. package/dist/setup-browser-C4eRV3h6.js +70 -0
  608. package/dist/setup-core-BnR486P-.js +143 -0
  609. package/dist/setup-core-CIswIiu5.js +166 -0
  610. package/dist/setup-core-CcbcrXXg.js +47 -0
  611. package/dist/setup-core-nZSw5BSv.js +205 -0
  612. package/dist/setup-surface-C5iSpT4M.js +490 -0
  613. package/dist/setup-wizard-helpers-r0J6l8ST.d.ts +203 -0
  614. package/dist/setup.finalize-adiRfo0U.js +522 -0
  615. package/dist/setup.gateway-config-BwFWKDfT.js +343 -0
  616. package/dist/shared-12TimyeF.js +182 -0
  617. package/dist/shared-9EWO34-k.js +298 -0
  618. package/dist/shared-B4vUbaRR.js +75 -0
  619. package/dist/shared-bNWpW3Dd.js +96 -0
  620. package/dist/shared-lU1y5dvS.js +102 -0
  621. package/dist/signal-DBlETRu9.js +114 -0
  622. package/dist/skills-Bio8GwTE.js +20 -0
  623. package/dist/skills-DE_MXFSN.js +853 -0
  624. package/dist/skills-cli-BGuW-tKw.js +292 -0
  625. package/dist/skills-install--rnorIoJ.js +763 -0
  626. package/dist/skills-status-B08PtBc_.js +21 -0
  627. package/dist/skills-status-CzM008aB.js +169 -0
  628. package/dist/slack-C4T53Nc-.js +114 -0
  629. package/dist/slash-commands.runtime-B7fsD8Be.js +128 -0
  630. package/dist/slash-dispatch.runtime-t0PAX4vQ.js +141 -0
  631. package/dist/slash-skill-commands.runtime-DIhPnEfR.js +116 -0
  632. package/dist/src-DrDirlvw.js +1701 -0
  633. package/dist/status-Bld14WSA.js +131 -0
  634. package/dist/status-CgeO4RuH.js +43 -0
  635. package/dist/status-HlvixAOq.js +606 -0
  636. package/dist/status-Rom_Lf3c.js +1599 -0
  637. package/dist/status-TwbMH6Am.js +126 -0
  638. package/dist/status-json-DMW7cmuK.js +288 -0
  639. package/dist/status.link-channel-V4LkB6Gq.js +143 -0
  640. package/dist/status.scan.deps.runtime-BE3X-dcP.js +126 -0
  641. package/dist/status.scan.runtime-BxVY4mty.js +119 -0
  642. package/dist/status.summary-CzLM0vVr.js +592 -0
  643. package/dist/status.summary.runtime-BSBnHZ1Q.js +118 -0
  644. package/dist/status.update-BxblMS7P.js +77 -0
  645. package/dist/subagent-orphan-recovery-BpRPryEj.js +307 -0
  646. package/dist/subagent-registry-runtime-DYYU5p3X.js +111 -0
  647. package/dist/subscription-CpFdxuFS.js +33 -0
  648. package/dist/subscription-DaA1urx-.js +102 -0
  649. package/dist/subscription-cli-Bvto9EmO.js +134 -0
  650. package/dist/synology-chat-3nwk-Nj0.js +297 -0
  651. package/dist/system-cli-BvNps8sl.js +94 -0
  652. package/dist/telegram/audit.d.ts +1 -1
  653. package/dist/telegram/audit.js +1 -1
  654. package/dist/telegram/token.d.ts +1 -1
  655. package/dist/telegram/token.js +35 -35
  656. package/dist/telegram-RtKXoEsF.js +114 -0
  657. package/dist/text-chunking-BD5mQe2R.js +84 -0
  658. package/dist/text-chunking-DDUU_vAF.d.ts +79 -0
  659. package/dist/tlon-z-kYmJE-.js +433 -0
  660. package/dist/tui-cli-CzSK08Rh.js +137 -0
  661. package/dist/tui-wV7R1Tlc.js +3834 -0
  662. package/dist/types-2H_e7eWT.d.ts +45 -0
  663. package/dist/types-ZKnGUchG.d.ts +22692 -0
  664. package/dist/types.base-BFiQZ4J9.d.ts +188 -0
  665. package/dist/ui-BWVHreeR.js +31 -0
  666. package/dist/update-D1Wgh1Tj.js +1036 -0
  667. package/dist/update-cli-CZh99uyY.js +1503 -0
  668. package/dist/update-offset-store-D5xTdUr0.js +112 -0
  669. package/dist/update-runner-GbKfoCHs.js +1496 -0
  670. package/dist/upsert-with-lock-BZU7Le8n.js +33 -0
  671. package/dist/usage-Czgwvg0h.js +115 -0
  672. package/dist/web-CMczmL90.js +112 -0
  673. package/dist/web-shared-B5Q0mIJq.d.ts +45 -0
  674. package/dist/webhook-request-guards-CsKDhZJr.d.ts +76 -0
  675. package/dist/webhook-targets-BSmFtesN.js +181 -0
  676. package/dist/webhook-targets-CjxuEE9C.d.ts +106 -0
  677. package/dist/webhooks-cli-Wl9y6AWW.js +350 -0
  678. package/dist/whatsapp-VzRW8MdR.js +114 -0
  679. package/dist/whatsapp-actions-Cg1Wxv8W.js +167 -0
  680. package/dist/workspace-DJ_S272u.js +484 -0
  681. package/dist/workspace-DbZSqjw0.js +289 -0
  682. package/dist/workspace-cli-D93DLmAh.js +154 -0
  683. package/dist/workspace-dirs-CGeIPpGN.js +2003 -0
  684. package/dist/zalo-CK2dlGmu.d.ts +9 -0
  685. package/dist/zalo-Db7s2boL.js +415 -0
  686. package/dist/zalouser-Jh5YTJX3.js +30911 -0
  687. package/docs/reference/templates/AGENTS.dev.md +83 -0
  688. package/docs/reference/templates/AGENTS.md +219 -0
  689. package/docs/reference/templates/BOOT.md +11 -0
  690. package/docs/reference/templates/BOOTSTRAP.md +62 -0
  691. package/docs/reference/templates/HEARTBEAT.md +12 -0
  692. package/docs/reference/templates/IDENTITY.dev.md +47 -0
  693. package/docs/reference/templates/IDENTITY.md +29 -0
  694. package/docs/reference/templates/SOUL.dev.md +76 -0
  695. package/docs/reference/templates/SOUL.md +43 -0
  696. package/docs/reference/templates/TOOLS.dev.md +24 -0
  697. package/docs/reference/templates/TOOLS.md +47 -0
  698. package/docs/reference/templates/USER.dev.md +18 -0
  699. package/docs/reference/templates/USER.md +23 -0
  700. package/extensions/discord/src/monitor/allow-list.ts +8 -1
  701. package/extensions/discord/src/monitor/message-handler.preflight.ts +4 -1
  702. package/package.json +2 -1
  703. package/dist/accounts-CDr-lDaV.d.ts +0 -103
  704. package/dist/accounts-CS8U4v8C.js +0 -114
  705. package/dist/acp-cli-BGT0jXcC.js +0 -2093
  706. package/dist/actions.runtime-BfckTw6c.js +0 -119
  707. package/dist/actions.runtime-Cl9mBfqH.js +0 -133
  708. package/dist/agent-scope-C-YmLnnb.js +0 -208
  709. package/dist/agents-CydD54p8.js +0 -222
  710. package/dist/agents-DpQsZO6O.js +0 -853
  711. package/dist/agents.config-XU7IsYE-.js +0 -121
  712. package/dist/agents.config-ssoQXuvF.js +0 -17
  713. package/dist/allow-list-Cfn6lmMK.js +0 -81
  714. package/dist/allowlist-CCYXVpM9.js +0 -142
  715. package/dist/api-BoXoFKxy.js +0 -117
  716. package/dist/audit-Bv05N5o9.js +0 -787
  717. package/dist/audit-CIWW1Aqm.js +0 -54
  718. package/dist/audit-channel.collect.runtime-Bi7yrdcO.js +0 -605
  719. package/dist/audit-channel.runtime-C_NDweiW.js +0 -121
  720. package/dist/audit-extra.async-Dp7OKSXg.js +0 -813
  721. package/dist/audit-membership-runtime-B8FQ6VtN.js +0 -162
  722. package/dist/audit.deep.runtime-CXhobL6b.js +0 -25
  723. package/dist/audit.nondeep.runtime-CrEm3T16.js +0 -832
  724. package/dist/audit.runtime-CJPKj1Zg.js +0 -118
  725. package/dist/auth-Byfp0flq.js +0 -101
  726. package/dist/auth-choice-BgOjdeXN.js +0 -507
  727. package/dist/auth-choice-CD1Heq0M.js +0 -122
  728. package/dist/auth-choice-ePNfg0iQ.js +0 -268
  729. package/dist/auth-choice-options-BlewQWI0.js +0 -123
  730. package/dist/auth-choice-prompt-BP2b6aXz.js +0 -36
  731. package/dist/auth-choice-prompt-Cmwl4n97.js +0 -115
  732. package/dist/auth-choice.apply-helpers-Dq-nxuuX.js +0 -66
  733. package/dist/auth-choice.plugin-providers.runtime-B23kOUzQ.js +0 -119
  734. package/dist/auth-profiles-1kPLbBwI.js +0 -127823
  735. package/dist/auth-profiles.runtime-DAfSjku1.js +0 -116
  736. package/dist/banner-DeOsobLO.js +0 -342
  737. package/dist/bluebubbles-BsLGedBM.js +0 -64
  738. package/dist/bluebubbles-CnT9wiS4.d.ts +0 -6
  739. package/dist/bot-CuzVYwa_.d.ts +0 -478
  740. package/dist/brave-BoWimrLe.js +0 -24
  741. package/dist/browser-cli-D_S3wEYE.js +0 -1494
  742. package/dist/call-ByEzDJ1_.js +0 -640
  743. package/dist/call-CHCWVg-O.js +0 -39
  744. package/dist/channel-3VC0oOMu.js +0 -214
  745. package/dist/channel-B9fCBPiS.js +0 -207
  746. package/dist/channel-B9q775cM.js +0 -562
  747. package/dist/channel-BG3UK54j.js +0 -803
  748. package/dist/channel-BRQAdMML.js +0 -352
  749. package/dist/channel-BmlLp933.js +0 -1321
  750. package/dist/channel-By6KvdTG.js +0 -920
  751. package/dist/channel-C8rRsdf6.js +0 -226
  752. package/dist/channel-CLEDBbXE.js +0 -943
  753. package/dist/channel-CMvBAG7o.js +0 -306
  754. package/dist/channel-CmlxxjHY.js +0 -1598
  755. package/dist/channel-CqG6_xN0.js +0 -949
  756. package/dist/channel-DNueHKs92.js +0 -316
  757. package/dist/channel-DUtyN7BX.js +0 -4681
  758. package/dist/channel-DWD6GrfZ.js +0 -538
  759. package/dist/channel-DaRYMYzj.js +0 -619
  760. package/dist/channel-Dj6BgLp8.js +0 -575
  761. package/dist/channel-account-context-Ba3u5D21.js +0 -103
  762. package/dist/channel-crabk6Em.js +0 -542
  763. package/dist/channel-i8uqQaK2.js +0 -497
  764. package/dist/channel-options-xljvwHS2.js +0 -50
  765. package/dist/channel-plugin-ids-DAgknSG4.js +0 -26
  766. package/dist/channel-summary-dHTMCG75.js +0 -111
  767. package/dist/channel-xVWQ96Ni.js +0 -397
  768. package/dist/channel.runtime-B6PoZ4BV.js +0 -182
  769. package/dist/channel.runtime-BPZmo57e.js +0 -404
  770. package/dist/channel.runtime-B_1uGR-U.js +0 -199
  771. package/dist/channel.runtime-BiXnPU0d.js +0 -218
  772. package/dist/channel.runtime-BpvDc9sv.js +0 -870
  773. package/dist/channel.runtime-CUua3W80.js +0 -418
  774. package/dist/channel.runtime-CaCBTd0A.js +0 -179
  775. package/dist/channel.runtime-D0FfYvUj.js +0 -4011
  776. package/dist/channel.runtime-DhoJtpvJ.js +0 -241
  777. package/dist/channel.runtime-Kj9EXNE0.js +0 -127
  778. package/dist/channel.runtime-r4tPuPyh.js +0 -171
  779. package/dist/channel.setup-B7d_grfe.js +0 -6
  780. package/dist/channel.setup-C0vu1fhi.js +0 -9
  781. package/dist/channel.setup-CAI0FNHj.js +0 -11
  782. package/dist/channel.setup-CkDVwv5R.js +0 -57
  783. package/dist/channel.setup-Cpd00YqQ.js +0 -8
  784. package/dist/channel.setup-DbBz1-WT.js +0 -9
  785. package/dist/channel.setup-GZnAvD9g.js +0 -8
  786. package/dist/channels-5H484RSw.js +0 -1118
  787. package/dist/channels-BnPudfyx.js +0 -404
  788. package/dist/channels-cli-WIC-QeH_.js +0 -291
  789. package/dist/channels-status-issues-RDmzovJU.js +0 -16
  790. package/dist/clawbot-cli-BgutNwf8.js +0 -118
  791. package/dist/cleanup-utils-DBl1Aij1.js +0 -96
  792. package/dist/cli-1P7u6zqu.js +0 -154
  793. package/dist/command-registry-B8jovrws.js +0 -232
  794. package/dist/command-registry-DtDl1FVm.js +0 -14
  795. package/dist/command-secret-gateway-BgUo3FxJ.js +0 -111
  796. package/dist/compact.runtime-CXbXM0AU.js +0 -116
  797. package/dist/completion-cli-Cik_owAE.js +0 -17
  798. package/dist/completion-cli-RU3P2RSl.js +0 -445
  799. package/dist/config-5HUpB1L1.js +0 -31
  800. package/dist/config-cli-QHaUHoZI.js +0 -433
  801. package/dist/config-guard-C9Sn3pE-.js +0 -118
  802. package/dist/config-sW57gztj.js +0 -44
  803. package/dist/config-validation-5LkjIKNt.js +0 -262
  804. package/dist/config-value-CtTWALxG.js +0 -132
  805. package/dist/configure-BmR2TPLf.js +0 -243
  806. package/dist/configure-DaLN-5xM.js +0 -1100
  807. package/dist/control-ui-assets-CH3MYmAo.js +0 -232
  808. package/dist/control-ui-shared-CA77PTml.js +0 -29
  809. package/dist/core-CvDzLs7B.js +0 -150
  810. package/dist/core-jm751KJ9.d.ts +0 -87
  811. package/dist/cron-cli-tguLpzyq.js +0 -639
  812. package/dist/daemon-cli-ptosOkL8.js +0 -339
  813. package/dist/daemon-install-DzU4EnVa.js +0 -180
  814. package/dist/deliver-DwxFoHM3.js +0 -111
  815. package/dist/deliver-runtime-DOdDyaPI.js +0 -111
  816. package/dist/device-id-cli-GopvlxxZ.js +0 -52
  817. package/dist/device-identity-CRfhC3_s.js +0 -365
  818. package/dist/devices-cli-ain7ESqU.js +0 -342
  819. package/dist/diagnostic-D96Xaqrj.js +0 -310
  820. package/dist/directory-cli-fh1UxGgY.js +0 -311
  821. package/dist/directory-config-helpers-Coivm0Mt.d.ts +0 -38
  822. package/dist/directory.static-CKjJUNGl.js +0 -44
  823. package/dist/discord-CflhwDEM.js +0 -114
  824. package/dist/discovery-x0ZqY4AB.js +0 -48
  825. package/dist/dm-policy-shared-DKzsSLlO.d.ts +0 -95
  826. package/dist/dns-cli-DCHyKjGf.js +0 -217
  827. package/dist/docs-cli-D3OoqYSP.js +0 -174
  828. package/dist/doctor-completion-Bq2eP87s.js +0 -90
  829. package/dist/doctor-config-flow-D8XRG9Ku.js +0 -2437
  830. package/dist/doctor-config-flow-DGiF1HGc.js +0 -112
  831. package/dist/enable-0QSF4YGH.js +0 -24
  832. package/dist/exec-approvals-cli-Bncym0Gd.js +0 -421
  833. package/dist/feishu-C1dM8pl2.d.ts +0 -36
  834. package/dist/gateway-cli-DYscsmA-.js +0 -26437
  835. package/dist/gateway-install-token-CNv17ac9.js +0 -163
  836. package/dist/gateway-rpc-BGC1Rxvg.js +0 -26
  837. package/dist/gateway-runtime-D89mSQPB.js +0 -69
  838. package/dist/git-commit-CeLH5Ozm.js +0 -2
  839. package/dist/git-commit-DUKRiCP-.js +0 -177
  840. package/dist/googlechat-BgXeXjd1.js +0 -307
  841. package/dist/googlechat-CNZQb1jd.d.ts +0 -12
  842. package/dist/group-access-Deh1tVNr.d.ts +0 -61
  843. package/dist/health-BEjzWwaB.js +0 -570
  844. package/dist/health-FjqrWQL6.js +0 -113
  845. package/dist/heartbeat-summary-CfdSA9M1.js +0 -57
  846. package/dist/help-BZeVprq1.js +0 -81
  847. package/dist/hooks-06OUQvAV.d.ts +0 -6
  848. package/dist/hooks-cli-B7uGJs2O.js +0 -1000
  849. package/dist/hooks-status-CfceaUSg.js +0 -78
  850. package/dist/http-registry-DYskWhOr.d.ts +0 -20
  851. package/dist/identity-file-sshkKKIr.js +0 -60
  852. package/dist/image-generation-D4o3j8o6.d.ts +0 -9
  853. package/dist/imessage-BcV3WGx_.js +0 -31
  854. package/dist/imessage-Dhje7Ty-.js +0 -115
  855. package/dist/inbound-reply-dispatch-C73_7SOl.js +0 -71
  856. package/dist/inbound-reply-dispatch-D6_HNqH8.d.ts +0 -72
  857. package/dist/install-target-D7NRhfzc.js +0 -574
  858. package/dist/installs-Bj6jblqc.js +0 -532
  859. package/dist/io-CMfWWPXQ.js +0 -9738
  860. package/dist/io-CV844hAM.js +0 -29
  861. package/dist/irc-DKi1fDYI.js +0 -672
  862. package/dist/library-rygTG3oA.js +0 -112
  863. package/dist/lifecycle-core-BPlvShWY.js +0 -382
  864. package/dist/line-B8gTtl3Y.d.ts +0 -75
  865. package/dist/line-CGsemKWJ.js +0 -530
  866. package/dist/llm-slug-generator-DlhVyMqT.js +0 -67
  867. package/dist/logging-5wu9k6w4.js +0 -30
  868. package/dist/logging-CxP9suT8.js +0 -13
  869. package/dist/login-qr-BcDsiwHs.js +0 -233
  870. package/dist/login-qr-Y8pJ5yV4.js +0 -112
  871. package/dist/logs-cli-XI9oVXpH.js +0 -256
  872. package/dist/manager-runtime-DkIlXBhD.js +0 -111
  873. package/dist/manager.runtime-Q0q2rJCC.js +0 -715
  874. package/dist/manifest-registry-DAd0SRAP.js +0 -1329
  875. package/dist/matrix-BI0DBBrG.js +0 -1495
  876. package/dist/matrix-D2JoHzb4.d.ts +0 -68
  877. package/dist/matrix-DiABGjJR.js +0 -1269
  878. package/dist/mcp-cli-BOyn_DLL.js +0 -87
  879. package/dist/media-understanding.runtime-DjUa7Dka.js +0 -116
  880. package/dist/memory-cli-CJd_vl-Y.js +0 -111
  881. package/dist/memory-search-CEEItIFR.js +0 -17
  882. package/dist/memory-search-Cv1SBrn7.js +0 -204
  883. package/dist/method-scopes-CQE7-bZ-.js +0 -2452
  884. package/dist/model-auth-markers-BFoM4IPf.d.ts +0 -20
  885. package/dist/model-picker-D6_89XHg.js +0 -112
  886. package/dist/model-picker-Svaw-APs.js +0 -390
  887. package/dist/model-picker.runtime-Chi9nV7A.js +0 -125
  888. package/dist/model-selection-hL8i1Jbs.js +0 -653
  889. package/dist/model-suppression.runtime-DjWJZ0X-.js +0 -116
  890. package/dist/models-7qj1dG_W.js +0 -118
  891. package/dist/models-BPOB_xJF.js +0 -2514
  892. package/dist/models-cli-DdlOVUjS.js +0 -309
  893. package/dist/models-config-CBqUS-jX.js +0 -111
  894. package/dist/models-config.providers.discovery-Dc905FWG.d.ts +0 -18
  895. package/dist/moldclaw-root-D6PbhbZk.js +0 -88
  896. package/dist/monitor-BPYhkEqF.js +0 -782
  897. package/dist/monitor-BuTcQ24j.js +0 -3468
  898. package/dist/monitor-CuXvNhFh.js +0 -113
  899. package/dist/monitor-D-TqSIHF.js +0 -6823
  900. package/dist/monitor-DRSgo9u2.js +0 -3076
  901. package/dist/monitor-DcHch39z.js +0 -772
  902. package/dist/monitor-DsHBMrXp.js +0 -115
  903. package/dist/monitor-shared-CL8T4gt1.js +0 -444
  904. package/dist/msteams-7FMwTvQG.js +0 -852
  905. package/dist/node-cli-BCjaSCZM.js +0 -2503
  906. package/dist/node-resolve-D5Hvcgyx.js +0 -835
  907. package/dist/nodes-cli-Dd_SNbkt.js +0 -1380
  908. package/dist/nostr-DBTFTxKs.js +0 -8744
  909. package/dist/nostr-DLqaIuZx.d.ts +0 -7
  910. package/dist/npm-resolution-CYfb3MHG.js +0 -60
  911. package/dist/oauth-env-zPt5RywA.js +0 -10
  912. package/dist/onboard-BEFQQeig.js +0 -25
  913. package/dist/onboard-CJHNyxJh.js +0 -48
  914. package/dist/onboard-D_3UeLEN.js +0 -589
  915. package/dist/onboard-channels-B_JL0Djc.js +0 -1241
  916. package/dist/onboard-channels-CqZzHt2C.js +0 -205
  917. package/dist/onboard-custom-CER3Ggbq.js +0 -571
  918. package/dist/onboard-custom-bNRdGECb.js +0 -114
  919. package/dist/onboard-helpers-BK0Hsb7Y.js +0 -335
  920. package/dist/onboard-helpers-CXZ5RPoR.js +0 -113
  921. package/dist/onboard-hooks-1NsxEDjH.js +0 -72
  922. package/dist/onboard-remote-DuKhC_7W.js +0 -117
  923. package/dist/onboard-remote-OwRcDuB3.js +0 -181
  924. package/dist/onboard-search-Cy8dOq2W.js +0 -302
  925. package/dist/onboard-skills-D5phRa6r.js +0 -117
  926. package/dist/onboard-skills-c9qWCNe9.js +0 -133
  927. package/dist/outbound-media-CXKqTh2X.d.ts +0 -11
  928. package/dist/outbound-media-DYRO2vTD.js +0 -11
  929. package/dist/pairing-access-BwJu1mkk.d.ts +0 -21
  930. package/dist/pairing-cli-BOnv0TYn.js +0 -217
  931. package/dist/perplexity-EZwC3y2b.js +0 -24
  932. package/dist/persistent-dedupe-hNES5tS1.d.ts +0 -26
  933. package/dist/pi-model-discovery-runtime-BToY3A6K.js +0 -111
  934. package/dist/pi-tools.before-tool-call.runtime-D_acPtld.js +0 -381
  935. package/dist/plugin-install-CgJpSjYd.js +0 -184
  936. package/dist/plugin-install-Cl1A4EF6.js +0 -117
  937. package/dist/plugin-install-plan-Dc2Z4DeU.js +0 -49
  938. package/dist/plugin-registry-B1UaWrQD.js +0 -49
  939. package/dist/plugin-registry-Cy8biwnn.js +0 -113
  940. package/dist/plugins-CXwvg50F.js +0 -111
  941. package/dist/plugins-cli-Uvzp2aYV.js +0 -917
  942. package/dist/policy-DsMBbEe7.js +0 -143
  943. package/dist/preflight-audio.runtime-hWsZIYvc.js +0 -116
  944. package/dist/probe-CNsSf1Uf.js +0 -6329
  945. package/dist/probe-CqOIrPhb.js +0 -47
  946. package/dist/probe-DH6gDw-h.js +0 -129
  947. package/dist/probe-DM16PLf4.js +0 -21
  948. package/dist/probe-DvAEEWYr.js +0 -1793
  949. package/dist/probe-auth-COfgCble.js +0 -48
  950. package/dist/probe-auth-I_5TX1Eh.js +0 -40
  951. package/dist/program-Dz80sgTU.js +0 -253
  952. package/dist/prompt-select-styled-wQehwFxK.js +0 -2673
  953. package/dist/provider-api-key-auth.runtime-BR9GU4ya.js +0 -121
  954. package/dist/provider-auth-choice-CdhA84kr.js +0 -126
  955. package/dist/provider-auth-choice-helpers-kabp_0zA.js +0 -48
  956. package/dist/provider-auth-choice-preference-se3zAM_2.js +0 -189
  957. package/dist/provider-auth-choice.runtime-BMc8-xNQ.js +0 -123
  958. package/dist/provider-auth-choices-CYsCViGi.js +0 -57
  959. package/dist/provider-auth-guidance-CMjUWlNf.js +0 -34
  960. package/dist/provider-auth-result-5xgWoVGi.d.ts +0 -18
  961. package/dist/provider-models-BCId_Lfu.js +0 -2113
  962. package/dist/provider-models-D-eFl9oH.d.ts +0 -867
  963. package/dist/provider-ollama-setup-B6XJZ0So.js +0 -314
  964. package/dist/provider-ollama-setup-BF1vhob8.d.ts +0 -32
  965. package/dist/provider-onboard-BjXHP3IZ.d.ts +0 -40
  966. package/dist/provider-onboard-Ca0TaNud.js +0 -139
  967. package/dist/provider-runtime.runtime-DwwkHw_7.js +0 -111
  968. package/dist/provider-self-hosted-setup-BEKLVGpj.js +0 -182
  969. package/dist/provider-self-hosted-setup-BQ5BIlpi.d.ts +0 -61
  970. package/dist/provider-stream-DrUD69ai.js +0 -512
  971. package/dist/provider-usage-BgKHCnjr.js +0 -111
  972. package/dist/provider-usage-D8EZpFz9.js +0 -633
  973. package/dist/provider-wizard-DMdb-zj_.js +0 -152
  974. package/dist/push-apns-BPH6d4VV.js +0 -1038
  975. package/dist/pw-ai-DttfldtL.js +0 -1867
  976. package/dist/qmd-manager-CybcDUfk.js +0 -1570
  977. package/dist/qr-cli-8NcmJ8Ft.js +0 -369
  978. package/dist/qr-cli-DWe0Our3.js +0 -113
  979. package/dist/reactions-D6N0LR16.js +0 -281
  980. package/dist/read-only-account-inspect.discord.runtime-CqUWTRfl.js +0 -116
  981. package/dist/read-only-account-inspect.slack.runtime-9-jpln3q.js +0 -116
  982. package/dist/read-only-account-inspect.telegram.runtime-EKPI1D7n.js +0 -116
  983. package/dist/redact-snapshot-DwJEIVk9.js +0 -2663
  984. package/dist/register.agent-D3YdDirP.js +0 -439
  985. package/dist/register.backup-dR27qCuo.js +0 -625
  986. package/dist/register.configure-BjFhkkka.js +0 -252
  987. package/dist/register.maintenance-DiMQJIOa.js +0 -574
  988. package/dist/register.message-CdZsKYH1.js +0 -709
  989. package/dist/register.onboard-B0rV1eaO.js +0 -192
  990. package/dist/register.setup-wKMvohzo.js +0 -212
  991. package/dist/register.status-health-sessions-BJ68m6pt.js +0 -498
  992. package/dist/register.subclis-CnnrWt2a.js +0 -315
  993. package/dist/register.subclis-lSvTkC6z.js +0 -13
  994. package/dist/replies-BABt9b48.js +0 -110
  995. package/dist/resolve-channels-BqZFl2Ux.js +0 -262
  996. package/dist/resolve-channels-DjQLXb7B.js +0 -226
  997. package/dist/resolve-route-CSHDsa_m.js +0 -538
  998. package/dist/resolve-users-BG6HaSR5.js +0 -143
  999. package/dist/root-help-ohmaCyC_.js +0 -32
  1000. package/dist/routes-4k2kpvoT.js +0 -7097
  1001. package/dist/rpc-Cnwn4Q6L.js +0 -67
  1002. package/dist/run-main-VYlacKA0.js +0 -424
  1003. package/dist/runtime-Cy8pqYUB.d.ts +0 -26
  1004. package/dist/runtime-discord-ops.runtime-DafrU-rI.js +0 -9078
  1005. package/dist/runtime-slack-ops.runtime-CdXBKXwd.js +0 -4556
  1006. package/dist/runtime-telegram-ops.runtime-B12sF7gE.js +0 -133
  1007. package/dist/runtime-whatsapp-login.runtime-CqEudH37.js +0 -114
  1008. package/dist/runtime-whatsapp-outbound.runtime-D5m2qyn-.js +0 -117
  1009. package/dist/sandbox-cli-CHJiEWXB.js +0 -535
  1010. package/dist/search-manager-BtNC3-i_.js +0 -16
  1011. package/dist/search-manager-C7J7B3_a.js +0 -386
  1012. package/dist/secrets-cli-C6yIWBbN.js +0 -2070
  1013. package/dist/security-cli-BVu9BkjD.js +0 -575
  1014. package/dist/send-BSreC7rr.js +0 -631
  1015. package/dist/send-BsLHQG_B.js +0 -1025
  1016. package/dist/send-BuNhp8PH.js +0 -283
  1017. package/dist/send-DOCswVar.js +0 -100
  1018. package/dist/send-Dl0LLErk.js +0 -629
  1019. package/dist/server-node-events-Bq2067EG.js +0 -506
  1020. package/dist/server-y38L7N5H.js +0 -107
  1021. package/dist/sessions-BV8gXURR.js +0 -112
  1022. package/dist/sessions-dl1Kc-Ci.js +0 -218
  1023. package/dist/setup-DGszQH0_.js +0 -387
  1024. package/dist/setup-DR5rRw9y.d.ts +0 -37
  1025. package/dist/setup-binary-C17YnmA8.js +0 -406
  1026. package/dist/setup-browser-CPx-nEsr.js +0 -70
  1027. package/dist/setup-core-BByHN1ME.js +0 -143
  1028. package/dist/setup-core-C0KPlBmL.js +0 -47
  1029. package/dist/setup-core-Cq37G6of.js +0 -166
  1030. package/dist/setup-core-uO84_Y75.js +0 -205
  1031. package/dist/setup-surface-BEMi7Rmb.js +0 -490
  1032. package/dist/setup-wizard-helpers-BtuGx_gN.d.ts +0 -203
  1033. package/dist/setup.finalize-BzPBa8zW.js +0 -522
  1034. package/dist/setup.gateway-config-DdwkF-8e.js +0 -343
  1035. package/dist/shared-BCw4SKjB.js +0 -96
  1036. package/dist/shared-CjNzsULP.js +0 -75
  1037. package/dist/shared-Cu1BE7ZE.js +0 -298
  1038. package/dist/shared-DSClmyUn.js +0 -182
  1039. package/dist/shared-DyJdGH6y.js +0 -102
  1040. package/dist/signal-Dyv4NZsB.js +0 -114
  1041. package/dist/skills-CbB5b27M.js +0 -853
  1042. package/dist/skills-CnfI7Szw.js +0 -20
  1043. package/dist/skills-cli-CavB1f_3.js +0 -292
  1044. package/dist/skills-install-B1OBdgd0.js +0 -763
  1045. package/dist/skills-status-B3gAmIbW.js +0 -169
  1046. package/dist/skills-status-DrHhFgU9.js +0 -21
  1047. package/dist/slack-BRzqnoAz.js +0 -114
  1048. package/dist/slash-commands.runtime-BK88kgds.js +0 -128
  1049. package/dist/slash-dispatch.runtime-COGywwJE.js +0 -141
  1050. package/dist/slash-skill-commands.runtime-Ti4brxgh.js +0 -116
  1051. package/dist/src-DUR6OQxI.js +0 -1701
  1052. package/dist/status-C6dgQY9a.js +0 -131
  1053. package/dist/status-CNK0Q7QH.js +0 -606
  1054. package/dist/status-DBcX0DSC.js +0 -43
  1055. package/dist/status-DKgFgbwv.js +0 -1599
  1056. package/dist/status-Wn5lhNAc.js +0 -126
  1057. package/dist/status-json-D2EkWqAl.js +0 -288
  1058. package/dist/status.link-channel-D3ULIdEa.js +0 -143
  1059. package/dist/status.scan.deps.runtime-BsjWTAm4.js +0 -126
  1060. package/dist/status.scan.runtime-D4HbzROD.js +0 -119
  1061. package/dist/status.summary-C3YxPrDK.js +0 -592
  1062. package/dist/status.summary.runtime-DAkXPSaK.js +0 -118
  1063. package/dist/status.update-B4NnN9P1.js +0 -77
  1064. package/dist/subagent-orphan-recovery-QiQEBv36.js +0 -307
  1065. package/dist/subagent-registry-runtime-BJatPQFK.js +0 -111
  1066. package/dist/subscription-BhZORXN9.js +0 -100
  1067. package/dist/subscription-QEUjQRMv.js +0 -33
  1068. package/dist/subscription-cli-HrULlAgc.js +0 -134
  1069. package/dist/synology-chat-DB76GWMN.js +0 -297
  1070. package/dist/system-cli-D8jDwWuL.js +0 -94
  1071. package/dist/telegram-BHiiqKkQ.js +0 -114
  1072. package/dist/text-chunking-Baonm9Lu.js +0 -84
  1073. package/dist/text-chunking-Y3dPBOuZ.d.ts +0 -79
  1074. package/dist/tlon-DLESxNgD.js +0 -433
  1075. package/dist/tui-C75zi2Cl.js +0 -3834
  1076. package/dist/tui-cli-DFwx5e6i.js +0 -137
  1077. package/dist/types-BBJ3Qz7j.d.ts +0 -45
  1078. package/dist/types-Ckufs_BY.d.ts +0 -22692
  1079. package/dist/types.base-Cw0-zIvE.d.ts +0 -188
  1080. package/dist/ui-B55NOIB6.js +0 -31
  1081. package/dist/update--ojavYQ4.js +0 -1036
  1082. package/dist/update-cli-Cvj5aWYM.js +0 -1503
  1083. package/dist/update-offset-store-upatuWwX.js +0 -112
  1084. package/dist/update-runner-DHkY_-76.js +0 -1496
  1085. package/dist/upsert-with-lock-C171GLaR.js +0 -33
  1086. package/dist/usage-N3bxnbmt.js +0 -115
  1087. package/dist/web-RdvT7gKa.js +0 -112
  1088. package/dist/web-shared-HSGD3yGt.d.ts +0 -45
  1089. package/dist/webhook-request-guards-CosLyl01.d.ts +0 -76
  1090. package/dist/webhook-targets-Bfnag-du.js +0 -181
  1091. package/dist/webhook-targets-Di17rt8e.d.ts +0 -106
  1092. package/dist/webhooks-cli-ZpnXrq7G.js +0 -350
  1093. package/dist/whatsapp-DNTAyZHt.js +0 -114
  1094. package/dist/whatsapp-actions-o1zKQzKZ.js +0 -167
  1095. package/dist/workspace-CpWi5wPr.js +0 -479
  1096. package/dist/workspace-Ii7aRS7c.js +0 -289
  1097. package/dist/workspace-dirs-x10McA9t.js +0 -2003
  1098. package/dist/zalo-BN3VCrRY.d.ts +0 -9
  1099. package/dist/zalo-zm_bYCKg.js +0 -415
  1100. package/dist/zalouser-CvVEUvc5.js +0 -30911
  1101. /package/dist/{account-id-B3YSn4hl.d.ts → account-id-B8ce6G_4.d.ts} +0 -0
  1102. /package/dist/{acpx-CnNv70m2.d.ts → acpx-Ci50I9T2.d.ts} +0 -0
  1103. /package/dist/{agent-media-payload-DE2pEcsz.d.ts → agent-media-payload-en-gS5p6.d.ts} +0 -0
  1104. /package/dist/{allow-from-DPpHnT2A.d.ts → allow-from-cMeQ47Ot.d.ts} +0 -0
  1105. /package/dist/{allowlist-resolution-CLFiZ6nE.d.ts → allowlist-resolution-DoAWbfXV.d.ts} +0 -0
  1106. /package/dist/{bluebubbles-Duhu-Jer.d.ts → bluebubbles-C6yYmUl0.d.ts} +0 -0
  1107. /package/dist/{boolean-param-BhFjB3gp.d.ts → boolean-param-CdO2TFTk.d.ts} +0 -0
  1108. /package/dist/{channel-config-schema-DnnVMdjR.d.ts → channel-config-schema-Chp38wel.d.ts} +0 -0
  1109. /package/dist/{channel-policy-Baq-Z06b.d.ts → channel-policy-g2h6AbYQ.d.ts} +0 -0
  1110. /package/dist/{chat-type-DpiBgwuG.d.ts → chat-type-BLt59pPT.d.ts} +0 -0
  1111. /package/dist/{command-format-vi4xq8e8.d.ts → command-format-BDJC05Jp.d.ts} +0 -0
  1112. /package/dist/{diffs-DK7fVSDo.d.ts → diffs-D_iNKCyn.d.ts} +0 -0
  1113. /package/dist/{directory-runtime-BTLPaysA.d.ts → directory-runtime-DhMex6HY.d.ts} +0 -0
  1114. /package/dist/{exec-C01wtBHu.d.ts → exec-pjfUY4KM.d.ts} +0 -0
  1115. /package/dist/{gaxios-fetch-compat-wZ38b3w3.js → gaxios-fetch-compat-B_vtINdV.js} +0 -0
  1116. /package/dist/{history-CwXuP2TW.d.ts → history-aqSS5VGQ.d.ts} +0 -0
  1117. /package/dist/{inbound-envelope-SggrBs9m.d.ts → inbound-envelope-C5hWuZod.d.ts} +0 -0
  1118. /package/dist/{index-apAZHsDo.d.ts → index-DXVQFYGX.d.ts} +0 -0
  1119. /package/dist/{json-store-r75IZGk9.d.ts → json-store-UnqQ5aV3.d.ts} +0 -0
  1120. /package/dist/{keyed-async-queue-DHIr7yNe.d.ts → keyed-async-queue-guucpLw3.d.ts} +0 -0
  1121. /package/dist/{links-HeQ3r_L0.d.ts → links-Bar0meEK.d.ts} +0 -0
  1122. /package/dist/{markdown-to-line-CDb4Jy3V.d.ts → markdown-to-line-D8uH_KOj.d.ts} +0 -0
  1123. /package/dist/{mattermost-DtCsxpgg.d.ts → mattermost-xl7jAFJL.d.ts} +0 -0
  1124. /package/dist/{net-BATPDwdQ.d.ts → net-rGOKGds6.d.ts} +0 -0
  1125. /package/dist/{nextcloud-talk-Bb2wHOwp.d.ts → nextcloud-talk-De2CZ9dV.d.ts} +0 -0
  1126. /package/dist/{oauth-utils-u567CLT0.d.ts → oauth-utils-DzN1AlEH.d.ts} +0 -0
  1127. /package/dist/{parse-finite-number-l3tNlrZh.d.ts → parse-finite-number-odgyqhi0.d.ts} +0 -0
  1128. /package/dist/{provider-usage.types-C6061OVN.d.ts → provider-usage.types-EDE9o-H_.d.ts} +0 -0
  1129. /package/dist/{reply-history-BDsFnZFl.d.ts → reply-history-CVuU31xe.d.ts} +0 -0
  1130. /package/dist/{reply-payload-CCvM4W9u.d.ts → reply-payload-CHkpBYwL.d.ts} +0 -0
  1131. /package/dist/{request-url-C54l4-xC.d.ts → request-url-DHisbiHY.d.ts} +0 -0
  1132. /package/dist/{run-command-D3RqWcHu.d.ts → run-command-y0Cndsb1.d.ts} +0 -0
  1133. /package/dist/{secret-input-schema-BLBt-NAP.d.ts → secret-input-schema-b1vpYDQN.d.ts} +0 -0
  1134. /package/dist/{session-key-BQ2-bR-9.d.ts → session-key-DTHQl57f.d.ts} +0 -0
  1135. /package/dist/{ssh-config-C4mcH9Ly.js → ssh-config-hEHBfU2_.js} +0 -0
  1136. /package/dist/{testing-DLkhGsoz.d.ts → testing-DszuZXgK.d.ts} +0 -0
  1137. /package/dist/{thinking-DRkjX18p.d.ts → thinking-IwXTGSeT.d.ts} +0 -0
  1138. /package/dist/{tool-send-CMMD1uDu.d.ts → tool-send-DWHRmKpz.d.ts} +0 -0
  1139. /package/dist/{vllm-defaults-CcGuf4hL.d.ts → vllm-defaults-CrxZgE6-.d.ts} +0 -0
  1140. /package/dist/{wait-Daog8bxM.d.ts → wait-wDWw_MTI.d.ts} +0 -0
  1141. /package/dist/{webhook-memory-guards-C5MrExwT.d.ts → webhook-memory-guards-DreORuJy.d.ts} +0 -0
  1142. /package/dist/{windows-spawn-j2l-dqu8.d.ts → windows-spawn-BIzH92x2.d.ts} +0 -0
  1143. /package/dist/{zod-schema.agent-runtime-krMrBnIn.d.ts → zod-schema.agent-runtime-CP2rmis3.d.ts} +0 -0
  1144. /package/dist/{zod-schema.core-BNDieZDZ.d.ts → zod-schema.core-Foi1tYwi.d.ts} +0 -0
@@ -1,4681 +0,0 @@
1
- import { t as formatDocsLink } from "./links-BcahUP5U.js";
2
- import { Ah as createScopedPairingAccess, Ew as applySetupAccountConfigPatch, HC as createAccountListHelpers, Is as formatInboundFromLabel$1, KC as buildComputedAccountStatusSnapshot, LT as collectAllowlistProviderRestrictSendersWarnings, Nd as resolveDmGroupAccessWithLists, Od as DM_GROUP_ACCESS_REASON, Pd as resolveEffectiveAllowFromLists, Qg as buildPendingHistoryContextFromMap, Tw as applyAccountNameToChannelSection, UT as deleteAccountFromConfigSection, Ug as logInboundDrop, Uw as resolveControlCommandGate, Vs as buildModelsProviderData, WS as createDedupeCache, WT as setAccountEnabledInConfigSection, Wg as logTypingFailure, Za as resolveStoredModelOverride, ag as createTypingCallbacks, bS as readRequestBodyWithLimit, dT as createScopedAccountConfigAccessors, e_ as clearHistoryEntriesIfEnabled, ec as listSkillCommandsForAgents, ig as buildSecretInputSchema, jh as buildAgentMediaPayload, kT as buildAccountScopedDmSecurityPolicy, kd as readStoreAllowFromForDmPolicy, kw as migrateBaseNameToDefaultAccount, nT as resolveAllowlistMatchSimple, nh as resolveChannelMediaMaxBytes, ow as resolveChannelGroupRequireMention, qy as getAgentScopedMediaLocalRoots, rC as loadSessionStore, r_ as recordPendingHistoryEntryIfEnabled, sg as createReplyPrefixOptions, tC as isDangerousNameMatchingEnabled, vS as isRequestBodyLimitError, xs as registerPluginHttpRoute } from "./auth-profiles-1kPLbBwI.js";
3
- import { w as normalizeProviderId } from "./model-selection-hL8i1Jbs.js";
4
- import { d as resolveThreadSessionKeys$1 } from "./session-key-UoG7Kfw5.js";
5
- import { n as normalizeAccountId, t as DEFAULT_ACCOUNT_ID } from "./account-id-BuyZMNja.js";
6
- import { a as hasConfiguredSecretInput, c as normalizeResolvedSecretInputString, l as normalizeSecretInputString } from "./types.secrets-Ca-9L8vU.js";
7
- import { F as requireOpenAllowFrom, a as DmPolicySchema, c as GroupPolicySchema, m as MarkdownConfigSchema, n as BlockStreamingCoalesceSchema } from "./zod-schema.core-DvwgNmpd.js";
8
- import { a as isTrustedProxyAddress, l as resolveClientIp } from "./net-K181nxTH.js";
9
- import { t as rawDataToString } from "./ws-Bd4lOIoI.js";
10
- import { c as resolveAllowlistProviderRuntimeGroupPolicy, f as warnMissingProviderGroupPolicyFallbackOnce, i as evaluateSenderGroupAccessForPolicy, l as resolveDefaultGroupPolicy } from "./group-access-UAqUyJod.js";
11
- import { r as buildChannelConfigSchema } from "./config-schema-BNU4GQh_.js";
12
- import { i as parseStrictPositiveInteger } from "./parse-finite-number-U5TetQpk.js";
13
- import { l as resolveStorePath } from "./paths-ApLcu1Uu.js";
14
- import { n as formatNormalizedAllowFromEntries } from "./allow-from-Brz0jyla.js";
15
- import { t as createPluginRuntimeStore } from "./runtime-store-DTqHvPYo.js";
16
- import { t as createAccountStatusSink } from "./channel-lifecycle-DA5pCpey.js";
17
- import { t as loadOutboundMediaFromUrl } from "./outbound-media-DYRO2vTD.js";
18
- import { n as buildPassiveProbedChannelStatusSummary } from "./channel-status-summary-DfvQV-Ir.js";
19
- import { t as requireChannelOpenAllowFrom } from "./config-schema-helpers-D4ZGZ7dA.js";
20
- import { createHash, createHmac, timingSafeEqual } from "node:crypto";
21
- import { z } from "zod";
22
- import WebSocket$1 from "ws";
23
- //#region extensions/mattermost/src/config-schema.ts
24
- const MattermostSlashCommandsSchema = z.object({
25
- native: z.union([z.boolean(), z.literal("auto")]).optional(),
26
- nativeSkills: z.union([z.boolean(), z.literal("auto")]).optional(),
27
- callbackPath: z.string().optional(),
28
- callbackUrl: z.string().optional()
29
- }).strict().optional();
30
- const MattermostAccountSchemaBase = z.object({
31
- name: z.string().optional(),
32
- capabilities: z.array(z.string()).optional(),
33
- dangerouslyAllowNameMatching: z.boolean().optional(),
34
- markdown: MarkdownConfigSchema,
35
- enabled: z.boolean().optional(),
36
- configWrites: z.boolean().optional(),
37
- botToken: buildSecretInputSchema().optional(),
38
- baseUrl: z.string().optional(),
39
- chatmode: z.enum([
40
- "oncall",
41
- "onmessage",
42
- "onchar"
43
- ]).optional(),
44
- oncharPrefixes: z.array(z.string()).optional(),
45
- requireMention: z.boolean().optional(),
46
- dmPolicy: DmPolicySchema.optional().default("pairing"),
47
- allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
48
- groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
49
- groupPolicy: GroupPolicySchema.optional().default("allowlist"),
50
- textChunkLimit: z.number().int().positive().optional(),
51
- chunkMode: z.enum(["length", "newline"]).optional(),
52
- blockStreaming: z.boolean().optional(),
53
- blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
54
- replyToMode: z.enum([
55
- "off",
56
- "first",
57
- "all"
58
- ]).optional(),
59
- responsePrefix: z.string().optional(),
60
- actions: z.object({ reactions: z.boolean().optional() }).optional(),
61
- commands: MattermostSlashCommandsSchema,
62
- interactions: z.object({
63
- callbackBaseUrl: z.string().optional(),
64
- allowedSourceIps: z.array(z.string()).optional()
65
- }).optional()
66
- }).strict();
67
- const MattermostAccountSchema = MattermostAccountSchemaBase.superRefine((value, ctx) => {
68
- requireChannelOpenAllowFrom({
69
- channel: "mattermost",
70
- policy: value.dmPolicy,
71
- allowFrom: value.allowFrom,
72
- ctx,
73
- requireOpenAllowFrom
74
- });
75
- });
76
- const MattermostConfigSchema = MattermostAccountSchemaBase.extend({
77
- accounts: z.record(z.string(), MattermostAccountSchema.optional()).optional(),
78
- defaultAccount: z.string().optional()
79
- }).superRefine((value, ctx) => {
80
- requireChannelOpenAllowFrom({
81
- channel: "mattermost",
82
- policy: value.dmPolicy,
83
- allowFrom: value.allowFrom,
84
- ctx,
85
- requireOpenAllowFrom
86
- });
87
- });
88
- //#endregion
89
- //#region extensions/mattermost/src/mattermost/client.ts
90
- function normalizeMattermostBaseUrl(raw) {
91
- const trimmed = raw?.trim();
92
- if (!trimmed) return;
93
- return trimmed.replace(/\/+$/, "").replace(/\/api\/v4$/i, "");
94
- }
95
- function buildMattermostApiUrl(baseUrl, path) {
96
- const normalized = normalizeMattermostBaseUrl(baseUrl);
97
- if (!normalized) throw new Error("Mattermost baseUrl is required");
98
- return `${normalized}/api/v4${path.startsWith("/") ? path : `/${path}`}`;
99
- }
100
- async function readMattermostError(res) {
101
- if ((res.headers.get("content-type") ?? "").includes("application/json")) {
102
- const data = await res.json();
103
- if (data?.message) return data.message;
104
- return JSON.stringify(data);
105
- }
106
- return await res.text();
107
- }
108
- function createMattermostClient(params) {
109
- const baseUrl = normalizeMattermostBaseUrl(params.baseUrl);
110
- if (!baseUrl) throw new Error("Mattermost baseUrl is required");
111
- const apiBaseUrl = `${baseUrl}/api/v4`;
112
- const token = params.botToken.trim();
113
- const fetchImpl = params.fetchImpl ?? fetch;
114
- const request = async (path, init) => {
115
- const url = buildMattermostApiUrl(baseUrl, path);
116
- const headers = new Headers(init?.headers);
117
- headers.set("Authorization", `Bearer ${token}`);
118
- if (typeof init?.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
119
- const res = await fetchImpl(url, {
120
- ...init,
121
- headers
122
- });
123
- if (!res.ok) {
124
- const detail = await readMattermostError(res);
125
- throw new Error(`Mattermost API ${res.status} ${res.statusText}: ${detail || "unknown error"}`);
126
- }
127
- if (res.status === 204) return;
128
- if ((res.headers.get("content-type") ?? "").includes("application/json")) return await res.json();
129
- return await res.text();
130
- };
131
- return {
132
- baseUrl,
133
- apiBaseUrl,
134
- token,
135
- request
136
- };
137
- }
138
- async function fetchMattermostMe(client) {
139
- return await client.request("/users/me");
140
- }
141
- async function fetchMattermostUser(client, userId) {
142
- return await client.request(`/users/${userId}`);
143
- }
144
- async function fetchMattermostUserByUsername(client, username) {
145
- return await client.request(`/users/username/${encodeURIComponent(username)}`);
146
- }
147
- async function fetchMattermostChannel(client, channelId) {
148
- return await client.request(`/channels/${channelId}`);
149
- }
150
- async function fetchMattermostChannelByName(client, teamId, channelName) {
151
- return await client.request(`/teams/${teamId}/channels/name/${encodeURIComponent(channelName)}`);
152
- }
153
- async function sendMattermostTyping(client, params) {
154
- const payload = { channel_id: params.channelId };
155
- const parentId = params.parentId?.trim();
156
- if (parentId) payload.parent_id = parentId;
157
- await client.request("/users/me/typing", {
158
- method: "POST",
159
- body: JSON.stringify(payload)
160
- });
161
- }
162
- async function createMattermostDirectChannel(client, userIds) {
163
- return await client.request("/channels/direct", {
164
- method: "POST",
165
- body: JSON.stringify(userIds)
166
- });
167
- }
168
- async function createMattermostPost(client, params) {
169
- const payload = {
170
- channel_id: params.channelId,
171
- message: params.message
172
- };
173
- if (params.rootId) payload.root_id = params.rootId;
174
- if (params.fileIds?.length) payload.file_ids = params.fileIds;
175
- if (params.props) payload.props = params.props;
176
- return await client.request("/posts", {
177
- method: "POST",
178
- body: JSON.stringify(payload)
179
- });
180
- }
181
- async function fetchMattermostUserTeams(client, userId) {
182
- return await client.request(`/users/${userId}/teams`);
183
- }
184
- async function updateMattermostPost(client, postId, params) {
185
- const payload = { id: postId };
186
- if (params.message !== void 0) payload.message = params.message;
187
- if (params.props !== void 0) payload.props = params.props;
188
- return await client.request(`/posts/${postId}`, {
189
- method: "PUT",
190
- body: JSON.stringify(payload)
191
- });
192
- }
193
- async function uploadMattermostFile(client, params) {
194
- const form = new FormData();
195
- const fileName = params.fileName?.trim() || "upload";
196
- const bytes = Uint8Array.from(params.buffer);
197
- const blob = params.contentType ? new Blob([bytes], { type: params.contentType }) : new Blob([bytes]);
198
- form.append("files", blob, fileName);
199
- form.append("channel_id", params.channelId);
200
- const res = await fetch(`${client.apiBaseUrl}/files`, {
201
- method: "POST",
202
- headers: { Authorization: `Bearer ${client.token}` },
203
- body: form
204
- });
205
- if (!res.ok) {
206
- const detail = await readMattermostError(res);
207
- throw new Error(`Mattermost API ${res.status} ${res.statusText}: ${detail || "unknown error"}`);
208
- }
209
- const info = (await res.json()).file_infos?.[0];
210
- if (!info?.id) throw new Error("Mattermost file upload failed");
211
- return info;
212
- }
213
- //#endregion
214
- //#region extensions/mattermost/src/mattermost/accounts.ts
215
- const { listAccountIds: listMattermostAccountIds, resolveDefaultAccountId: resolveDefaultMattermostAccountId } = createAccountListHelpers("mattermost");
216
- function resolveAccountConfig(cfg, accountId) {
217
- const accounts = cfg.channels?.mattermost?.accounts;
218
- if (!accounts || typeof accounts !== "object") return;
219
- return accounts[accountId];
220
- }
221
- function mergeMattermostAccountConfig(cfg, accountId) {
222
- const { accounts: _ignored, defaultAccount: _ignoredDefaultAccount, ...base } = cfg.channels?.mattermost ?? {};
223
- const account = resolveAccountConfig(cfg, accountId) ?? {};
224
- const mergedCommands = {
225
- ...base.commands ?? {},
226
- ...account.commands ?? {}
227
- };
228
- const merged = {
229
- ...base,
230
- ...account
231
- };
232
- if (Object.keys(mergedCommands).length > 0) merged.commands = mergedCommands;
233
- return merged;
234
- }
235
- function resolveMattermostRequireMention(config) {
236
- if (config.chatmode === "oncall") return true;
237
- if (config.chatmode === "onmessage") return false;
238
- if (config.chatmode === "onchar") return true;
239
- return config.requireMention;
240
- }
241
- function resolveMattermostAccount(params) {
242
- const accountId = normalizeAccountId(params.accountId);
243
- const baseEnabled = params.cfg.channels?.mattermost?.enabled !== false;
244
- const merged = mergeMattermostAccountConfig(params.cfg, accountId);
245
- const accountEnabled = merged.enabled !== false;
246
- const enabled = baseEnabled && accountEnabled;
247
- const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
248
- const envToken = allowEnv ? process.env.MATTERMOST_BOT_TOKEN?.trim() : void 0;
249
- const envUrl = allowEnv ? process.env.MATTERMOST_URL?.trim() : void 0;
250
- const configToken = params.allowUnresolvedSecretRef ? normalizeSecretInputString(merged.botToken) : normalizeResolvedSecretInputString({
251
- value: merged.botToken,
252
- path: `channels.mattermost.accounts.${accountId}.botToken`
253
- });
254
- const configUrl = merged.baseUrl?.trim();
255
- const botToken = configToken || envToken;
256
- const baseUrl = normalizeMattermostBaseUrl(configUrl || envUrl);
257
- const requireMention = resolveMattermostRequireMention(merged);
258
- const botTokenSource = configToken ? "config" : envToken ? "env" : "none";
259
- const baseUrlSource = configUrl ? "config" : envUrl ? "env" : "none";
260
- return {
261
- accountId,
262
- enabled,
263
- name: merged.name?.trim() || void 0,
264
- botToken,
265
- baseUrl,
266
- botTokenSource,
267
- baseUrlSource,
268
- config: merged,
269
- chatmode: merged.chatmode,
270
- oncharPrefixes: merged.oncharPrefixes,
271
- requireMention,
272
- textChunkLimit: merged.textChunkLimit,
273
- blockStreaming: merged.blockStreaming,
274
- blockStreamingCoalesce: merged.blockStreamingCoalesce
275
- };
276
- }
277
- /**
278
- * Resolve the effective replyToMode for a given chat type.
279
- * Mattermost auto-threading only applies to channel and group messages.
280
- */
281
- function resolveMattermostReplyToMode(account, kind) {
282
- if (kind === "direct") return "off";
283
- return account.config.replyToMode ?? "off";
284
- }
285
- //#endregion
286
- //#region extensions/mattermost/src/group-mentions.ts
287
- function resolveMattermostGroupRequireMention(params) {
288
- const account = resolveMattermostAccount({
289
- cfg: params.cfg,
290
- accountId: params.accountId
291
- });
292
- const requireMentionOverride = typeof params.requireMentionOverride === "boolean" ? params.requireMentionOverride : account.requireMention;
293
- return resolveChannelGroupRequireMention({
294
- cfg: params.cfg,
295
- channel: "mattermost",
296
- groupId: params.groupId,
297
- accountId: params.accountId,
298
- requireMentionOverride
299
- });
300
- }
301
- //#endregion
302
- //#region extensions/mattermost/src/mattermost/directory.ts
303
- function buildClient(params) {
304
- const account = resolveMattermostAccount({
305
- cfg: params.cfg,
306
- accountId: params.accountId
307
- });
308
- if (!account.enabled || !account.botToken || !account.baseUrl) return null;
309
- return createMattermostClient({
310
- baseUrl: account.baseUrl,
311
- botToken: account.botToken
312
- });
313
- }
314
- /**
315
- * Build clients from ALL enabled accounts (deduplicated by token).
316
- *
317
- * We always scan every account because:
318
- * - Private channels are only visible to bots that are members
319
- * - The requesting agent's account may have an expired/invalid token
320
- *
321
- * This means a single healthy bot token is enough for directory discovery.
322
- */
323
- function buildClients(params) {
324
- const accountIds = listMattermostAccountIds(params.cfg);
325
- const seen = /* @__PURE__ */ new Set();
326
- const clients = [];
327
- for (const id of accountIds) {
328
- const client = buildClient({
329
- cfg: params.cfg,
330
- accountId: id
331
- });
332
- if (client && !seen.has(client.token)) {
333
- seen.add(client.token);
334
- clients.push(client);
335
- }
336
- }
337
- return clients;
338
- }
339
- /**
340
- * List channels (public + private) visible to any configured bot account.
341
- *
342
- * NOTE: Uses per_page=200 which covers most instances. Mattermost does not
343
- * return a "has more" indicator, so very large instances (200+ channels per bot)
344
- * may see incomplete results. Pagination can be added if needed.
345
- */
346
- async function listMattermostDirectoryGroups(params) {
347
- const clients = buildClients(params);
348
- if (!clients.length) return [];
349
- const q = params.query?.trim().toLowerCase() || "";
350
- const seenIds = /* @__PURE__ */ new Set();
351
- const entries = [];
352
- for (const client of clients) try {
353
- const me = await fetchMattermostMe(client);
354
- const channels = await client.request(`/users/${me.id}/channels?per_page=200`);
355
- for (const ch of channels) {
356
- if (ch.type !== "O" && ch.type !== "P") continue;
357
- if (seenIds.has(ch.id)) continue;
358
- if (q) {
359
- const name = (ch.name ?? "").toLowerCase();
360
- const display = (ch.display_name ?? "").toLowerCase();
361
- if (!name.includes(q) && !display.includes(q)) continue;
362
- }
363
- seenIds.add(ch.id);
364
- entries.push({
365
- kind: "group",
366
- id: `channel:${ch.id}`,
367
- name: ch.name ?? void 0,
368
- handle: ch.display_name ?? void 0
369
- });
370
- }
371
- } catch (err) {
372
- console.debug?.("[mattermost-directory] listGroups: skipping account:", err?.message);
373
- continue;
374
- }
375
- return params.limit && params.limit > 0 ? entries.slice(0, params.limit) : entries;
376
- }
377
- /**
378
- * List team members as peer directory entries.
379
- *
380
- * Uses only the first available client since all bots in a team see the same
381
- * user list (unlike channels where membership varies). Uses the first team
382
- * returned — multi-team setups will only see members from that team.
383
- *
384
- * NOTE: per_page=200 for member listing; same pagination caveat as groups.
385
- */
386
- async function listMattermostDirectoryPeers(params) {
387
- const clients = buildClients(params);
388
- if (!clients.length) return [];
389
- const client = clients[0];
390
- try {
391
- const me = await fetchMattermostMe(client);
392
- const teams = await client.request("/users/me/teams");
393
- if (!teams.length) return [];
394
- const teamId = teams[0].id;
395
- const q = params.query?.trim().toLowerCase() || "";
396
- let users;
397
- if (q) users = await client.request("/users/search", {
398
- method: "POST",
399
- body: JSON.stringify({
400
- term: q,
401
- team_id: teamId
402
- })
403
- });
404
- else {
405
- const userIds = (await client.request(`/teams/${teamId}/members?per_page=200`)).map((m) => m.user_id).filter((id) => id !== me.id);
406
- if (!userIds.length) return [];
407
- users = await client.request("/users/ids", {
408
- method: "POST",
409
- body: JSON.stringify(userIds)
410
- });
411
- }
412
- const entries = users.filter((u) => u.id !== me.id).map((u) => ({
413
- kind: "user",
414
- id: `user:${u.id}`,
415
- name: u.username ?? void 0,
416
- handle: [u.first_name, u.last_name].filter(Boolean).join(" ").trim() || u.nickname || void 0
417
- }));
418
- return params.limit && params.limit > 0 ? entries.slice(0, params.limit) : entries;
419
- } catch (err) {
420
- console.debug?.("[mattermost-directory] listPeers failed:", err?.message);
421
- return [];
422
- }
423
- }
424
- //#endregion
425
- //#region extensions/mattermost/src/runtime.ts
426
- const { setRuntime: setMattermostRuntime, getRuntime: getMattermostRuntime } = createPluginRuntimeStore("Mattermost runtime not initialized");
427
- //#endregion
428
- //#region extensions/mattermost/src/mattermost/interactions.ts
429
- const INTERACTION_MAX_BODY_BYTES = 64 * 1024;
430
- const INTERACTION_BODY_TIMEOUT_MS = 1e4;
431
- const SIGNED_CHANNEL_ID_CONTEXT_KEY = "__moldclaw_channel_id";
432
- const callbackUrls = /* @__PURE__ */ new Map();
433
- function setInteractionCallbackUrl(accountId, url) {
434
- callbackUrls.set(accountId, url);
435
- }
436
- function resolveInteractionCallbackPath(accountId) {
437
- return `/mattermost/interactions/${accountId}`;
438
- }
439
- function isWildcardBindHost(rawHost) {
440
- const trimmed = rawHost.trim();
441
- if (!trimmed) return false;
442
- const host = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
443
- return host === "0.0.0.0" || host === "::" || host === "0:0:0:0:0:0:0:0" || host === "::0";
444
- }
445
- function normalizeCallbackBaseUrl(baseUrl) {
446
- return baseUrl.trim().replace(/\/+$/, "");
447
- }
448
- function headerValue(value) {
449
- if (Array.isArray(value)) return value[0]?.trim() || void 0;
450
- return value?.trim() || void 0;
451
- }
452
- function isAllowedInteractionSource(params) {
453
- const { allowedSourceIps } = params;
454
- if (!allowedSourceIps?.length) return true;
455
- return isTrustedProxyAddress(resolveClientIp({
456
- remoteAddr: params.req.socket?.remoteAddress,
457
- forwardedFor: headerValue(params.req.headers["x-forwarded-for"]),
458
- realIp: headerValue(params.req.headers["x-real-ip"]),
459
- trustedProxies: params.trustedProxies,
460
- allowRealIpFallback: params.allowRealIpFallback
461
- }), allowedSourceIps);
462
- }
463
- /**
464
- * Resolve the interaction callback URL for an account.
465
- * Falls back to computing it from interactions.callbackBaseUrl or gateway host config.
466
- */
467
- function computeInteractionCallbackUrl(accountId, cfg) {
468
- const path = resolveInteractionCallbackPath(accountId);
469
- const callbackBaseUrl = cfg?.interactions?.callbackBaseUrl?.trim() ?? cfg?.channels?.mattermost?.interactions?.callbackBaseUrl?.trim();
470
- if (callbackBaseUrl) return `${normalizeCallbackBaseUrl(callbackBaseUrl)}${path}`;
471
- const port = typeof cfg?.gateway?.port === "number" ? cfg.gateway.port : 18789;
472
- let host = cfg?.gateway?.customBindHost && !isWildcardBindHost(cfg.gateway.customBindHost) ? cfg.gateway.customBindHost.trim() : "localhost";
473
- if (host.includes(":") && !(host.startsWith("[") && host.endsWith("]"))) host = `[${host}]`;
474
- return `http://${host}:${port}${path}`;
475
- }
476
- /**
477
- * Resolve the interaction callback URL for an account.
478
- * Prefers the in-memory registered URL (set by the gateway monitor) so callers outside the
479
- * monitor lifecycle can reuse the runtime-validated callback destination.
480
- */
481
- function resolveInteractionCallbackUrl(accountId, cfg) {
482
- const cached = callbackUrls.get(accountId);
483
- if (cached) return cached;
484
- return computeInteractionCallbackUrl(accountId, cfg);
485
- }
486
- const interactionSecrets = /* @__PURE__ */ new Map();
487
- let defaultInteractionSecret;
488
- function deriveInteractionSecret(botToken) {
489
- return createHmac("sha256", "moldclaw-mattermost-interactions").update(botToken).digest("hex");
490
- }
491
- function setInteractionSecret(accountIdOrBotToken, botToken) {
492
- if (typeof botToken === "string") {
493
- interactionSecrets.set(accountIdOrBotToken, deriveInteractionSecret(botToken));
494
- return;
495
- }
496
- defaultInteractionSecret = deriveInteractionSecret(accountIdOrBotToken);
497
- }
498
- function getInteractionSecret(accountId) {
499
- const scoped = accountId ? interactionSecrets.get(accountId) : void 0;
500
- if (scoped) return scoped;
501
- if (defaultInteractionSecret) return defaultInteractionSecret;
502
- if (interactionSecrets.size === 1) {
503
- const first = interactionSecrets.values().next().value;
504
- if (typeof first === "string") return first;
505
- }
506
- throw new Error("Interaction secret not initialized — call setInteractionSecret(accountId, botToken) first");
507
- }
508
- function canonicalizeInteractionContext(value) {
509
- if (Array.isArray(value)) return value.map((item) => canonicalizeInteractionContext(item));
510
- if (value && typeof value === "object") {
511
- const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => [key, canonicalizeInteractionContext(entryValue)]);
512
- return Object.fromEntries(entries);
513
- }
514
- return value;
515
- }
516
- function generateInteractionToken(context, accountId) {
517
- const secret = getInteractionSecret(accountId);
518
- const payload = JSON.stringify(canonicalizeInteractionContext(context));
519
- return createHmac("sha256", secret).update(payload).digest("hex");
520
- }
521
- function verifyInteractionToken(context, token, accountId) {
522
- const expected = generateInteractionToken(context, accountId);
523
- if (expected.length !== token.length) return false;
524
- return timingSafeEqual(Buffer.from(expected), Buffer.from(token));
525
- }
526
- /**
527
- * Build Mattermost `props.attachments` with interactive buttons.
528
- *
529
- * Each button includes an HMAC token in its integration context so the
530
- * callback handler can verify the request originated from a legitimate
531
- * button click (Mattermost's recommended security pattern).
532
- */
533
- /**
534
- * Sanitize a button ID so Mattermost's action router can match it.
535
- * Mattermost uses the action ID in the URL path `/api/v4/posts/{id}/actions/{actionId}`
536
- * and IDs containing hyphens or underscores break the server-side routing.
537
- * See: https://github.com/mattermost/mattermost/issues/25747
538
- */
539
- function sanitizeActionId(id) {
540
- return id.replace(/[-_]/g, "");
541
- }
542
- function buildButtonAttachments(params) {
543
- const actions = params.buttons.map((btn) => {
544
- const safeId = sanitizeActionId(btn.id);
545
- const context = {
546
- action_id: safeId,
547
- ...btn.context
548
- };
549
- const token = generateInteractionToken(context, params.accountId);
550
- return {
551
- id: safeId,
552
- type: "button",
553
- name: btn.name,
554
- style: btn.style,
555
- integration: {
556
- url: params.callbackUrl,
557
- context: {
558
- ...context,
559
- _token: token
560
- }
561
- }
562
- };
563
- });
564
- return [{
565
- text: params.text ?? "",
566
- actions
567
- }];
568
- }
569
- function buildButtonProps(params) {
570
- const buttons = params.buttons.flatMap((item) => Array.isArray(item) ? item : [item]).map((btn) => ({
571
- id: String(btn.id ?? btn.callback_data ?? "").trim(),
572
- name: String(btn.text ?? btn.name ?? btn.label ?? "").trim(),
573
- style: btn.style ?? "default",
574
- context: typeof btn.context === "object" && btn.context !== null ? {
575
- ...btn.context,
576
- [SIGNED_CHANNEL_ID_CONTEXT_KEY]: params.channelId
577
- } : { [SIGNED_CHANNEL_ID_CONTEXT_KEY]: params.channelId }
578
- })).filter((btn) => btn.id && btn.name);
579
- if (buttons.length === 0) return;
580
- return { attachments: buildButtonAttachments({
581
- callbackUrl: params.callbackUrl,
582
- accountId: params.accountId,
583
- buttons,
584
- text: params.text
585
- }) };
586
- }
587
- function readInteractionBody(req) {
588
- return new Promise((resolve, reject) => {
589
- const chunks = [];
590
- let totalBytes = 0;
591
- const timer = setTimeout(() => {
592
- req.destroy();
593
- reject(/* @__PURE__ */ new Error("Request body read timeout"));
594
- }, INTERACTION_BODY_TIMEOUT_MS);
595
- req.on("data", (chunk) => {
596
- totalBytes += chunk.length;
597
- if (totalBytes > INTERACTION_MAX_BODY_BYTES) {
598
- req.destroy();
599
- clearTimeout(timer);
600
- reject(/* @__PURE__ */ new Error("Request body too large"));
601
- return;
602
- }
603
- chunks.push(chunk);
604
- });
605
- req.on("end", () => {
606
- clearTimeout(timer);
607
- resolve(Buffer.concat(chunks).toString("utf8"));
608
- });
609
- req.on("error", (err) => {
610
- clearTimeout(timer);
611
- reject(err);
612
- });
613
- });
614
- }
615
- function createMattermostInteractionHandler(params) {
616
- const { client, accountId, log } = params;
617
- const core = getMattermostRuntime();
618
- return async (req, res) => {
619
- if (req.method !== "POST") {
620
- res.statusCode = 405;
621
- res.setHeader("Allow", "POST");
622
- res.setHeader("Content-Type", "application/json");
623
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
624
- return;
625
- }
626
- if (!isAllowedInteractionSource({
627
- req,
628
- allowedSourceIps: params.allowedSourceIps,
629
- trustedProxies: params.trustedProxies,
630
- allowRealIpFallback: params.allowRealIpFallback
631
- })) {
632
- log?.(`mattermost interaction: rejected callback source remote=${req.socket?.remoteAddress ?? "?"}`);
633
- res.statusCode = 403;
634
- res.setHeader("Content-Type", "application/json");
635
- res.end(JSON.stringify({ error: "Forbidden origin" }));
636
- return;
637
- }
638
- let payload;
639
- try {
640
- const raw = await readInteractionBody(req);
641
- payload = JSON.parse(raw);
642
- } catch (err) {
643
- log?.(`mattermost interaction: failed to parse body: ${String(err)}`);
644
- res.statusCode = 400;
645
- res.setHeader("Content-Type", "application/json");
646
- res.end(JSON.stringify({ error: "Invalid request body" }));
647
- return;
648
- }
649
- const context = payload.context;
650
- if (!context) {
651
- res.statusCode = 400;
652
- res.setHeader("Content-Type", "application/json");
653
- res.end(JSON.stringify({ error: "Missing context" }));
654
- return;
655
- }
656
- const token = context._token;
657
- if (typeof token !== "string") {
658
- log?.("mattermost interaction: missing _token in context");
659
- res.statusCode = 403;
660
- res.setHeader("Content-Type", "application/json");
661
- res.end(JSON.stringify({ error: "Missing token" }));
662
- return;
663
- }
664
- const { _token, ...contextWithoutToken } = context;
665
- if (!verifyInteractionToken(contextWithoutToken, token, accountId)) {
666
- log?.("mattermost interaction: invalid _token");
667
- res.statusCode = 403;
668
- res.setHeader("Content-Type", "application/json");
669
- res.end(JSON.stringify({ error: "Invalid token" }));
670
- return;
671
- }
672
- const actionId = context.action_id;
673
- if (typeof actionId !== "string") {
674
- res.statusCode = 400;
675
- res.setHeader("Content-Type", "application/json");
676
- res.end(JSON.stringify({ error: "Missing action_id in context" }));
677
- return;
678
- }
679
- const signedChannelId = typeof contextWithoutToken[SIGNED_CHANNEL_ID_CONTEXT_KEY] === "string" ? contextWithoutToken[SIGNED_CHANNEL_ID_CONTEXT_KEY].trim() : "";
680
- if (signedChannelId && signedChannelId !== payload.channel_id) {
681
- log?.(`mattermost interaction: signed channel mismatch payload=${payload.channel_id} signed=${signedChannelId}`);
682
- res.statusCode = 403;
683
- res.setHeader("Content-Type", "application/json");
684
- res.end(JSON.stringify({ error: "Channel mismatch" }));
685
- return;
686
- }
687
- const userName = payload.user_name ?? payload.user_id;
688
- let originalMessage = "";
689
- let originalPost = null;
690
- let clickedButtonName = null;
691
- try {
692
- originalPost = await client.request(`/posts/${payload.post_id}`);
693
- const postChannelId = originalPost.channel_id?.trim();
694
- if (!postChannelId || postChannelId !== payload.channel_id) {
695
- log?.(`mattermost interaction: post channel mismatch payload=${payload.channel_id} post=${postChannelId ?? "<missing>"}`);
696
- res.statusCode = 403;
697
- res.setHeader("Content-Type", "application/json");
698
- res.end(JSON.stringify({ error: "Post/channel mismatch" }));
699
- return;
700
- }
701
- originalMessage = originalPost.message ?? "";
702
- const postAttachments = Array.isArray(originalPost?.props?.attachments) ? originalPost.props.attachments : [];
703
- for (const att of postAttachments) {
704
- const match = att.actions?.find((a) => a.id === actionId);
705
- if (match?.name) {
706
- clickedButtonName = match.name;
707
- break;
708
- }
709
- }
710
- if (clickedButtonName === null) {
711
- log?.(`mattermost interaction: action ${actionId} not found in post ${payload.post_id}`);
712
- res.statusCode = 403;
713
- res.setHeader("Content-Type", "application/json");
714
- res.end(JSON.stringify({ error: "Unknown action" }));
715
- return;
716
- }
717
- } catch (err) {
718
- log?.(`mattermost interaction: failed to validate post ${payload.post_id}: ${String(err)}`);
719
- res.statusCode = 500;
720
- res.setHeader("Content-Type", "application/json");
721
- res.end(JSON.stringify({ error: "Failed to validate interaction" }));
722
- return;
723
- }
724
- if (!originalPost) {
725
- log?.(`mattermost interaction: missing fetched post ${payload.post_id}`);
726
- res.statusCode = 500;
727
- res.setHeader("Content-Type", "application/json");
728
- res.end(JSON.stringify({ error: "Failed to load interaction post" }));
729
- return;
730
- }
731
- log?.(`mattermost interaction: action=${actionId} user=${payload.user_name ?? payload.user_id} post=${payload.post_id} channel=${payload.channel_id}`);
732
- if (params.authorizeButtonClick) try {
733
- const authorization = await params.authorizeButtonClick({
734
- payload,
735
- post: originalPost
736
- });
737
- if (!authorization.ok) {
738
- res.statusCode = authorization.statusCode ?? 200;
739
- res.setHeader("Content-Type", "application/json");
740
- res.end(JSON.stringify(authorization.response ?? { ephemeral_text: "You are not allowed to use this action here." }));
741
- return;
742
- }
743
- } catch (err) {
744
- log?.(`mattermost interaction: authorization failed: ${String(err)}`);
745
- res.statusCode = 500;
746
- res.setHeader("Content-Type", "application/json");
747
- res.end(JSON.stringify({ error: "Interaction authorization failed" }));
748
- return;
749
- }
750
- if (params.handleInteraction) try {
751
- const response = await params.handleInteraction({
752
- payload,
753
- userName,
754
- actionId,
755
- actionName: clickedButtonName,
756
- originalMessage,
757
- context: contextWithoutToken,
758
- post: originalPost
759
- });
760
- if (response !== null) {
761
- res.statusCode = 200;
762
- res.setHeader("Content-Type", "application/json");
763
- res.end(JSON.stringify(response));
764
- return;
765
- }
766
- } catch (err) {
767
- log?.(`mattermost interaction: custom handler failed: ${String(err)}`);
768
- res.statusCode = 500;
769
- res.setHeader("Content-Type", "application/json");
770
- res.end(JSON.stringify({ error: "Interaction handler failed" }));
771
- return;
772
- }
773
- try {
774
- const eventLabel = `Mattermost button click: action="${actionId}" by ${payload.user_name ?? payload.user_id} in channel ${payload.channel_id}`;
775
- const sessionKey = params.resolveSessionKey ? await params.resolveSessionKey({
776
- channelId: payload.channel_id,
777
- userId: payload.user_id,
778
- post: originalPost
779
- }) : `agent:main:mattermost:${accountId}:${payload.channel_id}`;
780
- core.system.enqueueSystemEvent(eventLabel, {
781
- sessionKey,
782
- contextKey: `mattermost:interaction:${payload.post_id}:${actionId}`
783
- });
784
- } catch (err) {
785
- log?.(`mattermost interaction: system event dispatch failed: ${String(err)}`);
786
- }
787
- try {
788
- await updateMattermostPost(client, payload.post_id, {
789
- message: originalMessage,
790
- props: { attachments: [{ text: `✓ **${clickedButtonName}** selected by @${userName}` }] }
791
- });
792
- } catch (err) {
793
- log?.(`mattermost interaction: failed to update post ${payload.post_id}: ${String(err)}`);
794
- }
795
- res.statusCode = 200;
796
- res.setHeader("Content-Type", "application/json");
797
- res.end("{}");
798
- if (params.dispatchButtonClick) try {
799
- await params.dispatchButtonClick({
800
- channelId: payload.channel_id,
801
- userId: payload.user_id,
802
- userName,
803
- actionId,
804
- actionName: clickedButtonName,
805
- postId: payload.post_id,
806
- post: originalPost
807
- });
808
- } catch (err) {
809
- log?.(`mattermost interaction: dispatchButtonClick failed: ${String(err)}`);
810
- }
811
- };
812
- }
813
- //#endregion
814
- //#region extensions/mattermost/src/mattermost/model-picker.ts
815
- const MATTERMOST_MODEL_PICKER_CONTEXT_KEY = "oc_model_picker";
816
- const MODELS_PAGE_SIZE = 8;
817
- const ACTION_IDS = {
818
- providers: "mdlprov",
819
- list: "mdllist",
820
- select: "mdlsel",
821
- back: "mdlback"
822
- };
823
- function splitModelRef(modelRef) {
824
- const match = (modelRef?.trim())?.match(/^([^/]+)\/(.+)$/u);
825
- if (!match) return null;
826
- const provider = normalizeProviderId(match[1]);
827
- const model = match[2].trim();
828
- if (!provider || !model) return null;
829
- return {
830
- provider,
831
- model
832
- };
833
- }
834
- function normalizePage(value) {
835
- if (!Number.isFinite(value)) return 1;
836
- return Math.max(1, Math.floor(value));
837
- }
838
- function paginateItems(items, page, pageSize = MODELS_PAGE_SIZE) {
839
- const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
840
- const safePage = Math.max(1, Math.min(normalizePage(page), totalPages));
841
- const start = (safePage - 1) * pageSize;
842
- return {
843
- items: items.slice(start, start + pageSize),
844
- page: safePage,
845
- totalPages,
846
- hasPrev: safePage > 1,
847
- hasNext: safePage < totalPages,
848
- totalItems: items.length
849
- };
850
- }
851
- function buildContext(state) {
852
- return {
853
- [MATTERMOST_MODEL_PICKER_CONTEXT_KEY]: true,
854
- ...state
855
- };
856
- }
857
- function buildButtonId(state) {
858
- const digest = createHash("sha256").update(JSON.stringify(state)).digest("hex").slice(0, 12);
859
- return `${ACTION_IDS[state.action]}${digest}`;
860
- }
861
- function buildButton(params) {
862
- const baseState = params.action === "providers" || params.action === "back" ? {
863
- action: params.action,
864
- ownerUserId: params.ownerUserId
865
- } : params.action === "list" ? {
866
- action: "list",
867
- ownerUserId: params.ownerUserId,
868
- provider: normalizeProviderId(params.provider ?? ""),
869
- page: normalizePage(params.page)
870
- } : {
871
- action: "select",
872
- ownerUserId: params.ownerUserId,
873
- provider: normalizeProviderId(params.provider ?? ""),
874
- page: normalizePage(params.page),
875
- model: String(params.model ?? "").trim()
876
- };
877
- return {
878
- id: buildButtonId(baseState),
879
- text: params.text,
880
- ...params.style ? { style: params.style } : {},
881
- context: buildContext(baseState)
882
- };
883
- }
884
- function getProviderModels(data, provider) {
885
- return [...data.byProvider.get(normalizeProviderId(provider)) ?? /* @__PURE__ */ new Set()].toSorted();
886
- }
887
- function formatCurrentModelLine(currentModel) {
888
- const parsed = splitModelRef(currentModel);
889
- if (!parsed) return "Current: default";
890
- return `Current: ${parsed.provider}/${parsed.model}`;
891
- }
892
- function resolveMattermostModelPickerEntry(commandText) {
893
- const normalized = commandText.trim().replace(/\s+/g, " ");
894
- if (/^\/model$/i.test(normalized)) return { kind: "summary" };
895
- if (/^\/models$/i.test(normalized)) return { kind: "providers" };
896
- const providerMatch = normalized.match(/^\/models\s+(\S+)$/i);
897
- if (!providerMatch?.[1]) return null;
898
- return {
899
- kind: "models",
900
- provider: normalizeProviderId(providerMatch[1])
901
- };
902
- }
903
- function parseMattermostModelPickerContext(context) {
904
- if (!context || context[MATTERMOST_MODEL_PICKER_CONTEXT_KEY] !== true) return null;
905
- const ownerUserId = String(context.ownerUserId ?? "").trim();
906
- const action = String(context.action ?? "").trim();
907
- if (!ownerUserId) return null;
908
- if (action === "providers" || action === "back") return {
909
- action,
910
- ownerUserId
911
- };
912
- const provider = normalizeProviderId(String(context.provider ?? ""));
913
- const page = Number.parseInt(String(context.page ?? "1"), 10);
914
- if (!provider) return null;
915
- if (action === "list") return {
916
- action,
917
- ownerUserId,
918
- provider,
919
- page: normalizePage(page)
920
- };
921
- if (action === "select") {
922
- const model = String(context.model ?? "").trim();
923
- if (!model) return null;
924
- return {
925
- action,
926
- ownerUserId,
927
- provider,
928
- page: normalizePage(page),
929
- model
930
- };
931
- }
932
- return null;
933
- }
934
- function buildMattermostAllowedModelRefs(data) {
935
- const refs = /* @__PURE__ */ new Set();
936
- for (const provider of data.providers) for (const model of data.byProvider.get(provider) ?? []) refs.add(`${provider}/${model}`);
937
- return refs;
938
- }
939
- function resolveMattermostModelPickerCurrentModel(params) {
940
- const fallback = `${params.data.resolvedDefault.provider}/${params.data.resolvedDefault.model}`;
941
- try {
942
- const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.route.agentId });
943
- const sessionStore = params.skipCache ? loadSessionStore(storePath, { skipCache: true }) : loadSessionStore(storePath);
944
- const sessionEntry = sessionStore[params.route.sessionKey];
945
- const override = resolveStoredModelOverride({
946
- sessionEntry,
947
- sessionStore,
948
- sessionKey: params.route.sessionKey
949
- });
950
- if (!override?.model) return fallback;
951
- const provider = (override.provider || params.data.resolvedDefault.provider).trim();
952
- return provider ? `${provider}/${override.model}` : fallback;
953
- } catch {
954
- return fallback;
955
- }
956
- }
957
- function renderMattermostModelSummaryView(params) {
958
- return {
959
- text: [
960
- formatCurrentModelLine(params.currentModel),
961
- "",
962
- "Tap below to browse models, or use:",
963
- "/oc_model <provider/model> to switch",
964
- "/oc_model status for details"
965
- ].join("\n"),
966
- buttons: [[buildButton({
967
- action: "providers",
968
- ownerUserId: params.ownerUserId,
969
- text: "Browse providers",
970
- style: "primary"
971
- })]]
972
- };
973
- }
974
- function renderMattermostProviderPickerView(params) {
975
- const currentProvider = splitModelRef(params.currentModel)?.provider;
976
- const rows = params.data.providers.map((provider) => [buildButton({
977
- action: "list",
978
- ownerUserId: params.ownerUserId,
979
- text: `${provider} (${params.data.byProvider.get(provider)?.size ?? 0})`,
980
- provider,
981
- page: 1,
982
- style: provider === currentProvider ? "primary" : "default"
983
- })]);
984
- return {
985
- text: [
986
- formatCurrentModelLine(params.currentModel),
987
- "",
988
- "Select a provider:"
989
- ].join("\n"),
990
- buttons: rows
991
- };
992
- }
993
- function renderMattermostModelsPickerView(params) {
994
- const provider = normalizeProviderId(params.provider);
995
- const models = getProviderModels(params.data, provider);
996
- const current = splitModelRef(params.currentModel);
997
- if (models.length === 0) return {
998
- text: [
999
- formatCurrentModelLine(params.currentModel),
1000
- "",
1001
- `Unknown provider: ${provider}`
1002
- ].join("\n"),
1003
- buttons: [[buildButton({
1004
- action: "back",
1005
- ownerUserId: params.ownerUserId,
1006
- text: "Back to providers"
1007
- })]]
1008
- };
1009
- const page = paginateItems(models, params.page);
1010
- const rows = page.items.map((model) => {
1011
- const isCurrent = current?.provider === provider && current.model === model;
1012
- return [buildButton({
1013
- action: "select",
1014
- ownerUserId: params.ownerUserId,
1015
- text: isCurrent ? `${model} [current]` : model,
1016
- provider,
1017
- model,
1018
- page: page.page,
1019
- style: isCurrent ? "primary" : "default"
1020
- })];
1021
- });
1022
- const navRow = [];
1023
- if (page.hasPrev) navRow.push(buildButton({
1024
- action: "list",
1025
- ownerUserId: params.ownerUserId,
1026
- text: "Prev",
1027
- provider,
1028
- page: page.page - 1
1029
- }));
1030
- if (page.hasNext) navRow.push(buildButton({
1031
- action: "list",
1032
- ownerUserId: params.ownerUserId,
1033
- text: "Next",
1034
- provider,
1035
- page: page.page + 1
1036
- }));
1037
- if (navRow.length > 0) rows.push(navRow);
1038
- rows.push([buildButton({
1039
- action: "back",
1040
- ownerUserId: params.ownerUserId,
1041
- text: "Back to providers"
1042
- })]);
1043
- return {
1044
- text: [
1045
- `Models (${provider}) - ${page.totalItems} available`,
1046
- formatCurrentModelLine(params.currentModel),
1047
- `Page ${page.page}/${page.totalPages}`,
1048
- "Select a model to switch immediately."
1049
- ].join("\n"),
1050
- buttons: rows
1051
- };
1052
- }
1053
- //#endregion
1054
- //#region extensions/mattermost/src/mattermost/monitor-auth.ts
1055
- function normalizeMattermostAllowEntry(entry) {
1056
- const trimmed = entry.trim();
1057
- if (!trimmed) return "";
1058
- if (trimmed === "*") return "*";
1059
- return trimmed.replace(/^(mattermost|user):/i, "").replace(/^@/, "").toLowerCase();
1060
- }
1061
- function normalizeMattermostAllowList(entries) {
1062
- const normalized = entries.map((entry) => normalizeMattermostAllowEntry(String(entry))).filter(Boolean);
1063
- return Array.from(new Set(normalized));
1064
- }
1065
- function resolveMattermostEffectiveAllowFromLists(params) {
1066
- return resolveEffectiveAllowFromLists({
1067
- allowFrom: normalizeMattermostAllowList(params.allowFrom ?? []),
1068
- groupAllowFrom: normalizeMattermostAllowList(params.groupAllowFrom ?? []),
1069
- storeAllowFrom: normalizeMattermostAllowList(params.storeAllowFrom ?? []),
1070
- dmPolicy: params.dmPolicy
1071
- });
1072
- }
1073
- function isMattermostSenderAllowed(params) {
1074
- const allowFrom = normalizeMattermostAllowList(params.allowFrom);
1075
- if (allowFrom.length === 0) return false;
1076
- return resolveAllowlistMatchSimple({
1077
- allowFrom,
1078
- senderId: normalizeMattermostAllowEntry(params.senderId),
1079
- senderName: params.senderName ? normalizeMattermostAllowEntry(params.senderName) : void 0,
1080
- allowNameMatching: params.allowNameMatching
1081
- }).allowed;
1082
- }
1083
- function mapMattermostChannelKind(channelType) {
1084
- const normalized = channelType?.trim().toUpperCase();
1085
- if (normalized === "D") return "direct";
1086
- if (normalized === "G" || normalized === "P") return "group";
1087
- return "channel";
1088
- }
1089
- function authorizeMattermostCommandInvocation(params) {
1090
- const { account, cfg, senderId, senderName, channelId, channelInfo, storeAllowFrom, allowTextCommands, hasControlCommand } = params;
1091
- if (!channelInfo) return {
1092
- ok: false,
1093
- denyReason: "unknown-channel",
1094
- commandAuthorized: false,
1095
- channelInfo: null,
1096
- kind: "channel",
1097
- chatType: "channel",
1098
- channelName: "",
1099
- channelDisplay: "",
1100
- roomLabel: `#${channelId}`
1101
- };
1102
- const kind = mapMattermostChannelKind(channelInfo.type);
1103
- const chatType = kind;
1104
- const channelName = channelInfo.name ?? "";
1105
- const channelDisplay = channelInfo.display_name ?? channelName;
1106
- const roomLabel = channelName ? `#${channelName}` : channelDisplay || `#${channelId}`;
1107
- const dmPolicy = account.config.dmPolicy ?? "pairing";
1108
- const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
1109
- const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
1110
- const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
1111
- const configAllowFrom = normalizeMattermostAllowList(account.config.allowFrom ?? []);
1112
- const configGroupAllowFrom = normalizeMattermostAllowList(account.config.groupAllowFrom ?? []);
1113
- const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveMattermostEffectiveAllowFromLists({
1114
- allowFrom: configAllowFrom,
1115
- groupAllowFrom: configGroupAllowFrom,
1116
- storeAllowFrom: normalizeMattermostAllowList(storeAllowFrom ?? []),
1117
- dmPolicy
1118
- });
1119
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
1120
- const commandDmAllowFrom = kind === "direct" ? effectiveAllowFrom : configAllowFrom;
1121
- const commandGroupAllowFrom = kind === "direct" ? effectiveGroupAllowFrom : configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom;
1122
- const senderAllowedForCommands = isMattermostSenderAllowed({
1123
- senderId,
1124
- senderName,
1125
- allowFrom: commandDmAllowFrom,
1126
- allowNameMatching
1127
- });
1128
- const groupAllowedForCommands = isMattermostSenderAllowed({
1129
- senderId,
1130
- senderName,
1131
- allowFrom: commandGroupAllowFrom,
1132
- allowNameMatching
1133
- });
1134
- const commandGate = resolveControlCommandGate({
1135
- useAccessGroups,
1136
- authorizers: [{
1137
- configured: commandDmAllowFrom.length > 0,
1138
- allowed: senderAllowedForCommands
1139
- }, {
1140
- configured: commandGroupAllowFrom.length > 0,
1141
- allowed: groupAllowedForCommands
1142
- }],
1143
- allowTextCommands,
1144
- hasControlCommand: allowTextCommands && hasControlCommand
1145
- });
1146
- const commandAuthorized = kind === "direct" ? dmPolicy === "open" || senderAllowedForCommands : commandGate.commandAuthorized;
1147
- if (kind === "direct") {
1148
- if (dmPolicy === "disabled") return {
1149
- ok: false,
1150
- denyReason: "dm-disabled",
1151
- commandAuthorized: false,
1152
- channelInfo,
1153
- kind,
1154
- chatType,
1155
- channelName,
1156
- channelDisplay,
1157
- roomLabel
1158
- };
1159
- if (dmPolicy !== "open" && !senderAllowedForCommands) return {
1160
- ok: false,
1161
- denyReason: dmPolicy === "pairing" ? "dm-pairing" : "unauthorized",
1162
- commandAuthorized: false,
1163
- channelInfo,
1164
- kind,
1165
- chatType,
1166
- channelName,
1167
- channelDisplay,
1168
- roomLabel
1169
- };
1170
- } else {
1171
- const senderGroupAccess = evaluateSenderGroupAccessForPolicy({
1172
- groupPolicy,
1173
- groupAllowFrom: effectiveGroupAllowFrom,
1174
- senderId,
1175
- isSenderAllowed: (_senderId, allowFrom) => isMattermostSenderAllowed({
1176
- senderId,
1177
- senderName,
1178
- allowFrom,
1179
- allowNameMatching
1180
- })
1181
- });
1182
- if (!senderGroupAccess.allowed && senderGroupAccess.reason === "disabled") return {
1183
- ok: false,
1184
- denyReason: "channels-disabled",
1185
- commandAuthorized: false,
1186
- channelInfo,
1187
- kind,
1188
- chatType,
1189
- channelName,
1190
- channelDisplay,
1191
- roomLabel
1192
- };
1193
- if (!senderGroupAccess.allowed && senderGroupAccess.reason === "empty_allowlist") return {
1194
- ok: false,
1195
- denyReason: "channel-no-allowlist",
1196
- commandAuthorized: false,
1197
- channelInfo,
1198
- kind,
1199
- chatType,
1200
- channelName,
1201
- channelDisplay,
1202
- roomLabel
1203
- };
1204
- if (!senderGroupAccess.allowed && senderGroupAccess.reason === "sender_not_allowlisted") return {
1205
- ok: false,
1206
- denyReason: "unauthorized",
1207
- commandAuthorized: false,
1208
- channelInfo,
1209
- kind,
1210
- chatType,
1211
- channelName,
1212
- channelDisplay,
1213
- roomLabel
1214
- };
1215
- if (commandGate.shouldBlock) return {
1216
- ok: false,
1217
- denyReason: "unauthorized",
1218
- commandAuthorized: false,
1219
- channelInfo,
1220
- kind,
1221
- chatType,
1222
- channelName,
1223
- channelDisplay,
1224
- roomLabel
1225
- };
1226
- }
1227
- return {
1228
- ok: true,
1229
- commandAuthorized,
1230
- channelInfo,
1231
- kind,
1232
- chatType,
1233
- channelName,
1234
- channelDisplay,
1235
- roomLabel
1236
- };
1237
- }
1238
- //#endregion
1239
- //#region extensions/mattermost/src/mattermost/monitor-helpers.ts
1240
- const formatInboundFromLabel = formatInboundFromLabel$1;
1241
- function resolveThreadSessionKeys(params) {
1242
- return resolveThreadSessionKeys$1({
1243
- ...params,
1244
- normalizeThreadId: (threadId) => threadId
1245
- });
1246
- }
1247
- /**
1248
- * Strip bot mention from message text while preserving newlines and
1249
- * block-level Markdown formatting (headings, lists, blockquotes).
1250
- */
1251
- function normalizeMention(text, mention) {
1252
- if (!mention) return text.trim();
1253
- const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1254
- const hasMentionRe = new RegExp(`@${escaped}\\b`, "i");
1255
- const leadingMentionRe = new RegExp(`^([\\t ]*)@${escaped}\\b[\\t ]*`, "i");
1256
- const trailingMentionRe = new RegExp(`[\\t ]*@${escaped}\\b[\\t ]*$`, "i");
1257
- const normalizedLines = text.split("\n").map((line) => {
1258
- const hadMention = hasMentionRe.test(line);
1259
- const normalizedLine = line.replace(leadingMentionRe, "$1").replace(trailingMentionRe, "").replace(new RegExp(`@${escaped}\\b`, "gi"), "").replace(/(\S)[ \t]{2,}/g, "$1 ");
1260
- return {
1261
- text: normalizedLine,
1262
- mentionOnlyBlank: hadMention && normalizedLine.trim() === ""
1263
- };
1264
- });
1265
- while (normalizedLines[0]?.mentionOnlyBlank) normalizedLines.shift();
1266
- while (normalizedLines.at(-1)?.text.trim() === "") normalizedLines.pop();
1267
- return normalizedLines.map((line) => line.text).join("\n");
1268
- }
1269
- //#endregion
1270
- //#region extensions/mattermost/src/mattermost/monitor-onchar.ts
1271
- const DEFAULT_ONCHAR_PREFIXES = [">", "!"];
1272
- function resolveOncharPrefixes(prefixes) {
1273
- const cleaned = prefixes?.map((entry) => entry.trim()).filter(Boolean) ?? DEFAULT_ONCHAR_PREFIXES;
1274
- return cleaned.length > 0 ? cleaned : DEFAULT_ONCHAR_PREFIXES;
1275
- }
1276
- function stripOncharPrefix(text, prefixes) {
1277
- const trimmed = text.trimStart();
1278
- for (const prefix of prefixes) {
1279
- if (!prefix) continue;
1280
- if (trimmed.startsWith(prefix)) return {
1281
- triggered: true,
1282
- stripped: trimmed.slice(prefix.length).trimStart()
1283
- };
1284
- }
1285
- return {
1286
- triggered: false,
1287
- stripped: text
1288
- };
1289
- }
1290
- //#endregion
1291
- //#region extensions/mattermost/src/mattermost/monitor-websocket.ts
1292
- var WebSocketClosedBeforeOpenError = class extends Error {
1293
- constructor(code, reason) {
1294
- super(`websocket closed before open (code ${code})`);
1295
- this.code = code;
1296
- this.reason = reason;
1297
- this.name = "WebSocketClosedBeforeOpenError";
1298
- }
1299
- };
1300
- const defaultMattermostWebSocketFactory = (url) => new WebSocket$1(url);
1301
- function parsePostedPayload(payload) {
1302
- if (payload.event !== "posted") return null;
1303
- const postData = payload.data?.post;
1304
- if (!postData) return null;
1305
- let post = null;
1306
- if (typeof postData === "string") try {
1307
- post = JSON.parse(postData);
1308
- } catch {
1309
- return null;
1310
- }
1311
- else if (typeof postData === "object") post = postData;
1312
- if (!post) return null;
1313
- return {
1314
- payload,
1315
- post
1316
- };
1317
- }
1318
- function createMattermostConnectOnce(opts) {
1319
- const webSocketFactory = opts.webSocketFactory ?? defaultMattermostWebSocketFactory;
1320
- return async () => {
1321
- const ws = webSocketFactory(opts.wsUrl);
1322
- const onAbort = () => ws.terminate();
1323
- opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
1324
- try {
1325
- return await new Promise((resolve, reject) => {
1326
- let opened = false;
1327
- let settled = false;
1328
- const resolveOnce = () => {
1329
- if (settled) return;
1330
- settled = true;
1331
- resolve();
1332
- };
1333
- const rejectOnce = (error) => {
1334
- if (settled) return;
1335
- settled = true;
1336
- reject(error);
1337
- };
1338
- ws.on("open", () => {
1339
- opened = true;
1340
- opts.statusSink?.({
1341
- connected: true,
1342
- lastConnectedAt: Date.now(),
1343
- lastError: null
1344
- });
1345
- ws.send(JSON.stringify({
1346
- seq: opts.nextSeq(),
1347
- action: "authentication_challenge",
1348
- data: { token: opts.botToken }
1349
- }));
1350
- });
1351
- ws.on("message", async (data) => {
1352
- const raw = rawDataToString(data);
1353
- let payload;
1354
- try {
1355
- payload = JSON.parse(raw);
1356
- } catch {
1357
- return;
1358
- }
1359
- if (payload.event === "reaction_added" || payload.event === "reaction_removed") {
1360
- if (!opts.onReaction) return;
1361
- try {
1362
- await opts.onReaction(payload);
1363
- } catch (err) {
1364
- opts.runtime.error?.(`mattermost reaction handler failed: ${String(err)}`);
1365
- }
1366
- return;
1367
- }
1368
- if (payload.event !== "posted") return;
1369
- const parsed = parsePostedPayload(payload);
1370
- if (!parsed) return;
1371
- try {
1372
- await opts.onPosted(parsed.post, parsed.payload);
1373
- } catch (err) {
1374
- opts.runtime.error?.(`mattermost handler failed: ${String(err)}`);
1375
- }
1376
- });
1377
- ws.on("close", (code, reason) => {
1378
- const message = reasonToString(reason);
1379
- opts.statusSink?.({
1380
- connected: false,
1381
- lastDisconnect: {
1382
- at: Date.now(),
1383
- status: code,
1384
- error: message || void 0
1385
- }
1386
- });
1387
- if (opened) {
1388
- resolveOnce();
1389
- return;
1390
- }
1391
- rejectOnce(new WebSocketClosedBeforeOpenError(code, message || void 0));
1392
- });
1393
- ws.on("error", (err) => {
1394
- opts.runtime.error?.(`mattermost websocket error: ${String(err)}`);
1395
- opts.statusSink?.({ lastError: String(err) });
1396
- try {
1397
- ws.close();
1398
- } catch {}
1399
- });
1400
- });
1401
- } finally {
1402
- opts.abortSignal?.removeEventListener("abort", onAbort);
1403
- }
1404
- };
1405
- }
1406
- function reasonToString(reason) {
1407
- if (!reason) return "";
1408
- if (typeof reason === "string") return reason;
1409
- return reason.length > 0 ? reason.toString("utf8") : "";
1410
- }
1411
- //#endregion
1412
- //#region extensions/mattermost/src/mattermost/reconnect.ts
1413
- /**
1414
- * Reconnection loop with exponential backoff.
1415
- *
1416
- * Calls `connectFn` in a while loop. On normal resolve (connection closed),
1417
- * the backoff resets. On thrown error (connection failed), the current delay is
1418
- * used, then doubled for the next retry.
1419
- * The loop exits when `abortSignal` fires.
1420
- */
1421
- async function runWithReconnect(connectFn, opts = {}) {
1422
- const { initialDelayMs = 2e3, maxDelayMs = 6e4 } = opts;
1423
- const jitterRatio = Math.max(0, opts.jitterRatio ?? 0);
1424
- const random = opts.random ?? Math.random;
1425
- let retryDelay = initialDelayMs;
1426
- let attempt = 0;
1427
- while (!opts.abortSignal?.aborted) {
1428
- let shouldIncreaseDelay = false;
1429
- let outcome = "resolved";
1430
- let error;
1431
- try {
1432
- await connectFn();
1433
- retryDelay = initialDelayMs;
1434
- } catch (err) {
1435
- if (opts.abortSignal?.aborted) return;
1436
- outcome = "rejected";
1437
- error = err;
1438
- opts.onError?.(err);
1439
- shouldIncreaseDelay = true;
1440
- }
1441
- if (opts.abortSignal?.aborted) return;
1442
- const delayMs = withJitter(retryDelay, jitterRatio, random);
1443
- if (!(opts.shouldReconnect?.({
1444
- attempt,
1445
- delayMs,
1446
- outcome,
1447
- error
1448
- }) ?? true)) return;
1449
- opts.onReconnect?.(delayMs);
1450
- await sleepAbortable(delayMs, opts.abortSignal);
1451
- if (shouldIncreaseDelay) retryDelay = Math.min(retryDelay * 2, maxDelayMs);
1452
- attempt++;
1453
- }
1454
- }
1455
- function withJitter(baseMs, jitterRatio, random) {
1456
- if (jitterRatio <= 0) return baseMs;
1457
- const normalized = Math.max(0, Math.min(1, random()));
1458
- const spread = baseMs * jitterRatio;
1459
- return Math.max(1, Math.round(baseMs - spread + normalized * spread * 2));
1460
- }
1461
- function sleepAbortable(ms, signal) {
1462
- return new Promise((resolve) => {
1463
- if (signal?.aborted) {
1464
- resolve();
1465
- return;
1466
- }
1467
- const onAbort = () => {
1468
- clearTimeout(timer);
1469
- resolve();
1470
- };
1471
- const timer = setTimeout(() => {
1472
- signal?.removeEventListener("abort", onAbort);
1473
- resolve();
1474
- }, ms);
1475
- signal?.addEventListener("abort", onAbort, { once: true });
1476
- });
1477
- }
1478
- //#endregion
1479
- //#region extensions/mattermost/src/mattermost/reply-delivery.ts
1480
- async function deliverMattermostReplyPayload(params) {
1481
- const mediaUrls = params.payload.mediaUrls ?? (params.payload.mediaUrl ? [params.payload.mediaUrl] : []);
1482
- const text = params.core.channel.text.convertMarkdownTables(params.payload.text ?? "", params.tableMode);
1483
- if (mediaUrls.length === 0) {
1484
- const chunkMode = params.core.channel.text.resolveChunkMode(params.cfg, "mattermost", params.accountId);
1485
- const chunks = params.core.channel.text.chunkMarkdownTextWithMode(text, params.textLimit, chunkMode);
1486
- for (const chunk of chunks.length > 0 ? chunks : [text]) {
1487
- if (!chunk) continue;
1488
- await params.sendMessage(params.to, chunk, {
1489
- accountId: params.accountId,
1490
- replyToId: params.replyToId
1491
- });
1492
- }
1493
- return;
1494
- }
1495
- const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.agentId);
1496
- let first = true;
1497
- for (const mediaUrl of mediaUrls) {
1498
- const caption = first ? text : "";
1499
- first = false;
1500
- await params.sendMessage(params.to, caption, {
1501
- accountId: params.accountId,
1502
- mediaUrl,
1503
- mediaLocalRoots,
1504
- replyToId: params.replyToId
1505
- });
1506
- }
1507
- }
1508
- //#endregion
1509
- //#region extensions/mattermost/src/mattermost/target-resolution.ts
1510
- const mattermostOpaqueTargetCache = /* @__PURE__ */ new Map();
1511
- function cacheKey$1(baseUrl, token, id) {
1512
- return `${baseUrl}::${token}::${id}`;
1513
- }
1514
- /** Mattermost IDs are 26-character lowercase alphanumeric strings. */
1515
- function isMattermostId(value) {
1516
- return /^[a-z0-9]{26}$/.test(value);
1517
- }
1518
- function isExplicitMattermostTarget(raw) {
1519
- const trimmed = raw.trim();
1520
- if (!trimmed) return false;
1521
- return /^(channel|user|mattermost):/i.test(trimmed) || trimmed.startsWith("@") || trimmed.startsWith("#");
1522
- }
1523
- function parseMattermostApiStatus(err) {
1524
- if (!err || typeof err !== "object") return;
1525
- const msg = "message" in err ? String(err.message ?? "") : "";
1526
- const match = /Mattermost API (\d{3})\b/.exec(msg);
1527
- if (!match) return;
1528
- const code = Number(match[1]);
1529
- return Number.isFinite(code) ? code : void 0;
1530
- }
1531
- async function resolveMattermostOpaqueTarget(params) {
1532
- const input = params.input.trim();
1533
- if (!input || isExplicitMattermostTarget(input) || !isMattermostId(input)) return null;
1534
- const account = params.cfg && (!params.token || !params.baseUrl) ? resolveMattermostAccount({
1535
- cfg: params.cfg,
1536
- accountId: params.accountId
1537
- }) : null;
1538
- const token = params.token?.trim() || account?.botToken?.trim();
1539
- const baseUrl = normalizeMattermostBaseUrl(params.baseUrl ?? account?.baseUrl);
1540
- if (!token || !baseUrl) return null;
1541
- const key = cacheKey$1(baseUrl, token, input);
1542
- const cached = mattermostOpaqueTargetCache.get(key);
1543
- if (cached === true) return {
1544
- kind: "user",
1545
- id: input,
1546
- to: `user:${input}`
1547
- };
1548
- if (cached === false) return {
1549
- kind: "channel",
1550
- id: input,
1551
- to: `channel:${input}`
1552
- };
1553
- const client = createMattermostClient({
1554
- baseUrl,
1555
- botToken: token
1556
- });
1557
- try {
1558
- await fetchMattermostUser(client, input);
1559
- mattermostOpaqueTargetCache.set(key, true);
1560
- return {
1561
- kind: "user",
1562
- id: input,
1563
- to: `user:${input}`
1564
- };
1565
- } catch (err) {
1566
- if (parseMattermostApiStatus(err) === 404) mattermostOpaqueTargetCache.set(key, false);
1567
- return {
1568
- kind: "channel",
1569
- id: input,
1570
- to: `channel:${input}`
1571
- };
1572
- }
1573
- }
1574
- //#endregion
1575
- //#region extensions/mattermost/src/mattermost/send.ts
1576
- const botUserCache = /* @__PURE__ */ new Map();
1577
- const userByNameCache = /* @__PURE__ */ new Map();
1578
- const channelByNameCache = /* @__PURE__ */ new Map();
1579
- const dmChannelCache = /* @__PURE__ */ new Map();
1580
- const getCore = () => getMattermostRuntime();
1581
- function cacheKey(baseUrl, token) {
1582
- return `${baseUrl}::${token}`;
1583
- }
1584
- function normalizeMessage(text, mediaUrl) {
1585
- return [text.trim(), mediaUrl?.trim()].filter(Boolean).join("\n");
1586
- }
1587
- function isHttpUrl(value) {
1588
- return /^https?:\/\//i.test(value);
1589
- }
1590
- function parseMattermostTarget(raw) {
1591
- const trimmed = raw.trim();
1592
- if (!trimmed) throw new Error("Recipient is required for Mattermost sends");
1593
- const lower = trimmed.toLowerCase();
1594
- if (lower.startsWith("channel:")) {
1595
- const id = trimmed.slice(8).trim();
1596
- if (!id) throw new Error("Channel id is required for Mattermost sends");
1597
- if (id.startsWith("#")) {
1598
- const name = id.slice(1).trim();
1599
- if (!name) throw new Error("Channel name is required for Mattermost sends");
1600
- return {
1601
- kind: "channel-name",
1602
- name
1603
- };
1604
- }
1605
- if (!isMattermostId(id)) return {
1606
- kind: "channel-name",
1607
- name: id
1608
- };
1609
- return {
1610
- kind: "channel",
1611
- id
1612
- };
1613
- }
1614
- if (lower.startsWith("user:")) {
1615
- const id = trimmed.slice(5).trim();
1616
- if (!id) throw new Error("User id is required for Mattermost sends");
1617
- return {
1618
- kind: "user",
1619
- id
1620
- };
1621
- }
1622
- if (lower.startsWith("mattermost:")) {
1623
- const id = trimmed.slice(11).trim();
1624
- if (!id) throw new Error("User id is required for Mattermost sends");
1625
- return {
1626
- kind: "user",
1627
- id
1628
- };
1629
- }
1630
- if (trimmed.startsWith("@")) {
1631
- const username = trimmed.slice(1).trim();
1632
- if (!username) throw new Error("Username is required for Mattermost sends");
1633
- return {
1634
- kind: "user",
1635
- username
1636
- };
1637
- }
1638
- if (trimmed.startsWith("#")) {
1639
- const name = trimmed.slice(1).trim();
1640
- if (!name) throw new Error("Channel name is required for Mattermost sends");
1641
- return {
1642
- kind: "channel-name",
1643
- name
1644
- };
1645
- }
1646
- if (!isMattermostId(trimmed)) return {
1647
- kind: "channel-name",
1648
- name: trimmed
1649
- };
1650
- return {
1651
- kind: "channel",
1652
- id: trimmed
1653
- };
1654
- }
1655
- async function resolveBotUser(baseUrl, token) {
1656
- const key = cacheKey(baseUrl, token);
1657
- const cached = botUserCache.get(key);
1658
- if (cached) return cached;
1659
- const user = await fetchMattermostMe(createMattermostClient({
1660
- baseUrl,
1661
- botToken: token
1662
- }));
1663
- botUserCache.set(key, user);
1664
- return user;
1665
- }
1666
- async function resolveUserIdByUsername(params) {
1667
- const { baseUrl, token, username } = params;
1668
- const key = `${cacheKey(baseUrl, token)}::${username.toLowerCase()}`;
1669
- const cached = userByNameCache.get(key);
1670
- if (cached?.id) return cached.id;
1671
- const user = await fetchMattermostUserByUsername(createMattermostClient({
1672
- baseUrl,
1673
- botToken: token
1674
- }), username);
1675
- userByNameCache.set(key, user);
1676
- return user.id;
1677
- }
1678
- async function resolveChannelIdByName(params) {
1679
- const { baseUrl, token, name } = params;
1680
- const key = `${cacheKey(baseUrl, token)}::channel::${name.toLowerCase()}`;
1681
- const cached = channelByNameCache.get(key);
1682
- if (cached) return cached;
1683
- const client = createMattermostClient({
1684
- baseUrl,
1685
- botToken: token
1686
- });
1687
- const teams = await fetchMattermostUserTeams(client, (await fetchMattermostMe(client)).id);
1688
- for (const team of teams) try {
1689
- const channel = await fetchMattermostChannelByName(client, team.id, name);
1690
- if (channel?.id) {
1691
- channelByNameCache.set(key, channel.id);
1692
- return channel.id;
1693
- }
1694
- } catch {}
1695
- throw new Error(`Mattermost channel "#${name}" not found in any team the bot belongs to`);
1696
- }
1697
- async function resolveTargetChannelId(params) {
1698
- if (params.target.kind === "channel") return params.target.id;
1699
- if (params.target.kind === "channel-name") return await resolveChannelIdByName({
1700
- baseUrl: params.baseUrl,
1701
- token: params.token,
1702
- name: params.target.name
1703
- });
1704
- const userId = params.target.id ? params.target.id : await resolveUserIdByUsername({
1705
- baseUrl: params.baseUrl,
1706
- token: params.token,
1707
- username: params.target.username ?? ""
1708
- });
1709
- const dmKey = `${cacheKey(params.baseUrl, params.token)}::dm::${userId}`;
1710
- const cachedDm = dmChannelCache.get(dmKey);
1711
- if (cachedDm) return cachedDm;
1712
- const botUser = await resolveBotUser(params.baseUrl, params.token);
1713
- const channel = await createMattermostDirectChannel(createMattermostClient({
1714
- baseUrl: params.baseUrl,
1715
- botToken: params.token
1716
- }), [botUser.id, userId]);
1717
- dmChannelCache.set(dmKey, channel.id);
1718
- return channel.id;
1719
- }
1720
- async function resolveMattermostSendContext(to, opts = {}) {
1721
- const core = getCore();
1722
- const cfg = opts.cfg ?? core.config.loadConfig();
1723
- const account = resolveMattermostAccount({
1724
- cfg,
1725
- accountId: opts.accountId
1726
- });
1727
- const token = opts.botToken?.trim() || account.botToken?.trim();
1728
- if (!token) throw new Error(`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`);
1729
- const baseUrl = normalizeMattermostBaseUrl(opts.baseUrl ?? account.baseUrl);
1730
- if (!baseUrl) throw new Error(`Mattermost baseUrl missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.baseUrl or MATTERMOST_URL for default).`);
1731
- const trimmedTo = to?.trim() ?? "";
1732
- const opaqueTarget = await resolveMattermostOpaqueTarget({
1733
- input: trimmedTo,
1734
- token,
1735
- baseUrl
1736
- });
1737
- const channelId = await resolveTargetChannelId({
1738
- target: opaqueTarget?.kind === "user" ? {
1739
- kind: "user",
1740
- id: opaqueTarget.id
1741
- } : opaqueTarget?.kind === "channel" ? {
1742
- kind: "channel",
1743
- id: opaqueTarget.id
1744
- } : parseMattermostTarget(trimmedTo),
1745
- baseUrl,
1746
- token
1747
- });
1748
- return {
1749
- cfg,
1750
- accountId: account.accountId,
1751
- token,
1752
- baseUrl,
1753
- channelId
1754
- };
1755
- }
1756
- async function sendMessageMattermost(to, text, opts = {}) {
1757
- const core = getCore();
1758
- const logger = core.logging.getChildLogger({ module: "mattermost" });
1759
- const { cfg, accountId, token, baseUrl, channelId } = await resolveMattermostSendContext(to, opts);
1760
- const client = createMattermostClient({
1761
- baseUrl,
1762
- botToken: token
1763
- });
1764
- let props = opts.props;
1765
- if (!props && Array.isArray(opts.buttons) && opts.buttons.length > 0) {
1766
- setInteractionSecret(accountId, token);
1767
- props = buildButtonProps({
1768
- callbackUrl: resolveInteractionCallbackUrl(accountId, {
1769
- gateway: cfg.gateway,
1770
- interactions: resolveMattermostAccount({
1771
- cfg,
1772
- accountId
1773
- }).config?.interactions
1774
- }),
1775
- accountId,
1776
- channelId,
1777
- buttons: opts.buttons,
1778
- text: opts.attachmentText
1779
- });
1780
- }
1781
- let message = text?.trim() ?? "";
1782
- let fileIds;
1783
- let uploadError;
1784
- const mediaUrl = opts.mediaUrl?.trim();
1785
- if (mediaUrl) try {
1786
- const media = await loadOutboundMediaFromUrl(mediaUrl, { mediaLocalRoots: opts.mediaLocalRoots });
1787
- fileIds = [(await uploadMattermostFile(client, {
1788
- channelId,
1789
- buffer: media.buffer,
1790
- fileName: media.fileName ?? "upload",
1791
- contentType: media.contentType ?? void 0
1792
- })).id];
1793
- } catch (err) {
1794
- uploadError = err instanceof Error ? err : new Error(String(err));
1795
- if (core.logging.shouldLogVerbose()) logger.debug?.(`mattermost send: media upload failed, falling back to URL text: ${String(err)}`);
1796
- message = normalizeMessage(message, isHttpUrl(mediaUrl) ? mediaUrl : "");
1797
- }
1798
- if (message) {
1799
- const tableMode = core.channel.text.resolveMarkdownTableMode({
1800
- cfg,
1801
- channel: "mattermost",
1802
- accountId
1803
- });
1804
- message = core.channel.text.convertMarkdownTables(message, tableMode);
1805
- }
1806
- if (!message && (!fileIds || fileIds.length === 0)) {
1807
- if (uploadError) throw new Error(`Mattermost media upload failed: ${uploadError.message}`);
1808
- throw new Error("Mattermost message is empty");
1809
- }
1810
- const post = await createMattermostPost(client, {
1811
- channelId,
1812
- message,
1813
- rootId: opts.replyToId,
1814
- fileIds,
1815
- props
1816
- });
1817
- core.channel.activity.record({
1818
- channel: "mattermost",
1819
- accountId,
1820
- direction: "outbound"
1821
- });
1822
- return {
1823
- messageId: post.id ?? "unknown",
1824
- channelId
1825
- };
1826
- }
1827
- //#endregion
1828
- //#region extensions/mattermost/src/mattermost/slash-commands.ts
1829
- /**
1830
- * Built-in MoldClaw commands to register as native slash commands.
1831
- * These mirror the text-based commands already handled by the gateway.
1832
- */
1833
- const DEFAULT_COMMAND_SPECS = [
1834
- {
1835
- trigger: "oc_status",
1836
- originalName: "status",
1837
- description: "Show session status (model, usage, uptime)",
1838
- autoComplete: true
1839
- },
1840
- {
1841
- trigger: "oc_model",
1842
- originalName: "model",
1843
- description: "View or change the current model",
1844
- autoComplete: true,
1845
- autoCompleteHint: "[model-name]"
1846
- },
1847
- {
1848
- trigger: "oc_models",
1849
- originalName: "models",
1850
- description: "Browse available models",
1851
- autoComplete: true,
1852
- autoCompleteHint: "[provider]"
1853
- },
1854
- {
1855
- trigger: "oc_new",
1856
- originalName: "new",
1857
- description: "Start a new conversation session",
1858
- autoComplete: true
1859
- },
1860
- {
1861
- trigger: "oc_help",
1862
- originalName: "help",
1863
- description: "Show available commands",
1864
- autoComplete: true
1865
- },
1866
- {
1867
- trigger: "oc_think",
1868
- originalName: "think",
1869
- description: "Set thinking/reasoning level",
1870
- autoComplete: true,
1871
- autoCompleteHint: "[off|low|medium|high]"
1872
- },
1873
- {
1874
- trigger: "oc_reasoning",
1875
- originalName: "reasoning",
1876
- description: "Toggle reasoning mode",
1877
- autoComplete: true,
1878
- autoCompleteHint: "[on|off]"
1879
- },
1880
- {
1881
- trigger: "oc_verbose",
1882
- originalName: "verbose",
1883
- description: "Toggle verbose mode",
1884
- autoComplete: true,
1885
- autoCompleteHint: "[on|off]"
1886
- }
1887
- ];
1888
- /**
1889
- * List existing custom slash commands for a team.
1890
- */
1891
- async function listMattermostCommands(client, teamId) {
1892
- return await client.request(`/commands?team_id=${encodeURIComponent(teamId)}&custom_only=true`);
1893
- }
1894
- /**
1895
- * Create a custom slash command on a Mattermost team.
1896
- */
1897
- async function createMattermostCommand(client, params) {
1898
- return await client.request("/commands", {
1899
- method: "POST",
1900
- body: JSON.stringify(params)
1901
- });
1902
- }
1903
- /**
1904
- * Delete a custom slash command.
1905
- */
1906
- async function deleteMattermostCommand(client, commandId) {
1907
- await client.request(`/commands/${encodeURIComponent(commandId)}`, { method: "DELETE" });
1908
- }
1909
- /**
1910
- * Update an existing custom slash command.
1911
- */
1912
- async function updateMattermostCommand(client, params) {
1913
- return await client.request(`/commands/${encodeURIComponent(params.id)}`, {
1914
- method: "PUT",
1915
- body: JSON.stringify(params)
1916
- });
1917
- }
1918
- /**
1919
- * Register all MoldClaw slash commands for a given team.
1920
- * Skips commands that are already registered with the same trigger + callback URL.
1921
- * Returns the list of newly created command IDs.
1922
- */
1923
- async function registerSlashCommands(params) {
1924
- const { client, teamId, creatorUserId, callbackUrl, commands, log } = params;
1925
- const normalizedCreatorUserId = creatorUserId.trim();
1926
- if (!normalizedCreatorUserId) throw new Error("creatorUserId is required for slash command reconciliation");
1927
- let existing = [];
1928
- try {
1929
- existing = await listMattermostCommands(client, teamId);
1930
- } catch (err) {
1931
- log?.(`mattermost: failed to list existing commands: ${String(err)}`);
1932
- throw err;
1933
- }
1934
- const existingByTrigger = /* @__PURE__ */ new Map();
1935
- for (const cmd of existing) {
1936
- const list = existingByTrigger.get(cmd.trigger) ?? [];
1937
- list.push(cmd);
1938
- existingByTrigger.set(cmd.trigger, list);
1939
- }
1940
- const registered = [];
1941
- for (const spec of commands) {
1942
- const existingForTrigger = existingByTrigger.get(spec.trigger) ?? [];
1943
- const ownedCommands = existingForTrigger.filter((cmd) => cmd.creator_id?.trim() === normalizedCreatorUserId);
1944
- const foreignCommands = existingForTrigger.filter((cmd) => cmd.creator_id?.trim() !== normalizedCreatorUserId);
1945
- if (ownedCommands.length === 0 && foreignCommands.length > 0) {
1946
- log?.(`mattermost: trigger /${spec.trigger} already used by non-MoldClaw command(s); skipping to avoid mutating external integrations`);
1947
- continue;
1948
- }
1949
- if (ownedCommands.length > 1) log?.(`mattermost: multiple owned commands found for /${spec.trigger}; using the first and leaving extras untouched`);
1950
- const existingCmd = ownedCommands[0];
1951
- if (existingCmd && existingCmd.url === callbackUrl) {
1952
- log?.(`mattermost: command /${spec.trigger} already registered (id=${existingCmd.id})`);
1953
- registered.push({
1954
- id: existingCmd.id,
1955
- trigger: spec.trigger,
1956
- teamId,
1957
- token: existingCmd.token,
1958
- managed: false
1959
- });
1960
- continue;
1961
- }
1962
- if (existingCmd && existingCmd.url !== callbackUrl) {
1963
- log?.(`mattermost: command /${spec.trigger} exists with different callback URL; updating (id=${existingCmd.id})`);
1964
- try {
1965
- const updated = await updateMattermostCommand(client, {
1966
- id: existingCmd.id,
1967
- team_id: teamId,
1968
- trigger: spec.trigger,
1969
- method: "P",
1970
- url: callbackUrl,
1971
- description: spec.description,
1972
- auto_complete: spec.autoComplete,
1973
- auto_complete_desc: spec.description,
1974
- auto_complete_hint: spec.autoCompleteHint
1975
- });
1976
- registered.push({
1977
- id: updated.id,
1978
- trigger: spec.trigger,
1979
- teamId,
1980
- token: updated.token,
1981
- managed: false
1982
- });
1983
- continue;
1984
- } catch (err) {
1985
- log?.(`mattermost: failed to update command /${spec.trigger} (id=${existingCmd.id}): ${String(err)}`);
1986
- try {
1987
- await deleteMattermostCommand(client, existingCmd.id);
1988
- log?.(`mattermost: deleted stale command /${spec.trigger} (id=${existingCmd.id})`);
1989
- } catch (deleteErr) {
1990
- log?.(`mattermost: failed to delete stale command /${spec.trigger} (id=${existingCmd.id}): ${String(deleteErr)}`);
1991
- continue;
1992
- }
1993
- }
1994
- }
1995
- try {
1996
- const created = await createMattermostCommand(client, {
1997
- team_id: teamId,
1998
- trigger: spec.trigger,
1999
- method: "P",
2000
- url: callbackUrl,
2001
- description: spec.description,
2002
- auto_complete: spec.autoComplete,
2003
- auto_complete_desc: spec.description,
2004
- auto_complete_hint: spec.autoCompleteHint
2005
- });
2006
- log?.(`mattermost: registered command /${spec.trigger} (id=${created.id})`);
2007
- registered.push({
2008
- id: created.id,
2009
- trigger: spec.trigger,
2010
- teamId,
2011
- token: created.token,
2012
- managed: true
2013
- });
2014
- } catch (err) {
2015
- log?.(`mattermost: failed to register command /${spec.trigger}: ${String(err)}`);
2016
- }
2017
- }
2018
- return registered;
2019
- }
2020
- /**
2021
- * Clean up all registered slash commands.
2022
- */
2023
- async function cleanupSlashCommands(params) {
2024
- const { client, commands, log } = params;
2025
- for (const cmd of commands) {
2026
- if (!cmd.managed) continue;
2027
- try {
2028
- await deleteMattermostCommand(client, cmd.id);
2029
- log?.(`mattermost: deleted command /${cmd.trigger} (id=${cmd.id})`);
2030
- } catch (err) {
2031
- log?.(`mattermost: failed to delete command /${cmd.trigger}: ${String(err)}`);
2032
- }
2033
- }
2034
- }
2035
- /**
2036
- * Parse a Mattermost slash command callback payload from a URL-encoded or JSON body.
2037
- */
2038
- function parseSlashCommandPayload(body, contentType) {
2039
- if (!body) return null;
2040
- try {
2041
- if (contentType?.includes("application/json")) {
2042
- const parsed = JSON.parse(body);
2043
- const token = typeof parsed.token === "string" ? parsed.token : "";
2044
- const teamId = typeof parsed.team_id === "string" ? parsed.team_id : "";
2045
- const channelId = typeof parsed.channel_id === "string" ? parsed.channel_id : "";
2046
- const userId = typeof parsed.user_id === "string" ? parsed.user_id : "";
2047
- const command = typeof parsed.command === "string" ? parsed.command : "";
2048
- if (!token || !teamId || !channelId || !userId || !command) return null;
2049
- return {
2050
- token,
2051
- team_id: teamId,
2052
- team_domain: typeof parsed.team_domain === "string" ? parsed.team_domain : void 0,
2053
- channel_id: channelId,
2054
- channel_name: typeof parsed.channel_name === "string" ? parsed.channel_name : void 0,
2055
- user_id: userId,
2056
- user_name: typeof parsed.user_name === "string" ? parsed.user_name : void 0,
2057
- command,
2058
- text: typeof parsed.text === "string" ? parsed.text : "",
2059
- trigger_id: typeof parsed.trigger_id === "string" ? parsed.trigger_id : void 0,
2060
- response_url: typeof parsed.response_url === "string" ? parsed.response_url : void 0
2061
- };
2062
- }
2063
- const params = new URLSearchParams(body);
2064
- const token = params.get("token");
2065
- const teamId = params.get("team_id");
2066
- const channelId = params.get("channel_id");
2067
- const userId = params.get("user_id");
2068
- const command = params.get("command");
2069
- if (!token || !teamId || !channelId || !userId || !command) return null;
2070
- return {
2071
- token,
2072
- team_id: teamId,
2073
- team_domain: params.get("team_domain") ?? void 0,
2074
- channel_id: channelId,
2075
- channel_name: params.get("channel_name") ?? void 0,
2076
- user_id: userId,
2077
- user_name: params.get("user_name") ?? void 0,
2078
- command,
2079
- text: params.get("text") ?? "",
2080
- trigger_id: params.get("trigger_id") ?? void 0,
2081
- response_url: params.get("response_url") ?? void 0
2082
- };
2083
- } catch {
2084
- return null;
2085
- }
2086
- }
2087
- /**
2088
- * Map the trigger word back to the original MoldClaw command name.
2089
- * e.g. "oc_status" -> "/status", "oc_model" -> "/model"
2090
- */
2091
- function resolveCommandText(trigger, text, triggerMap) {
2092
- const commandName = triggerMap?.get(trigger) ?? (trigger.startsWith("oc_") ? trigger.slice(3) : trigger);
2093
- const args = text.trim();
2094
- return args ? `/${commandName} ${args}` : `/${commandName}`;
2095
- }
2096
- const DEFAULT_CALLBACK_PATH = "/api/channels/mattermost/command";
2097
- /**
2098
- * Ensure the callback path starts with a leading `/` to prevent
2099
- * malformed URLs like `http://host:portapi/...`.
2100
- */
2101
- function normalizeCallbackPath(path) {
2102
- const trimmed = path.trim();
2103
- if (!trimmed) return DEFAULT_CALLBACK_PATH;
2104
- return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
2105
- }
2106
- function resolveSlashCommandConfig(raw) {
2107
- return {
2108
- native: raw?.native ?? "auto",
2109
- nativeSkills: raw?.nativeSkills ?? "auto",
2110
- callbackPath: normalizeCallbackPath(raw?.callbackPath ?? DEFAULT_CALLBACK_PATH),
2111
- callbackUrl: raw?.callbackUrl?.trim() || void 0
2112
- };
2113
- }
2114
- function isSlashCommandsEnabled(config) {
2115
- if (config.native === true) return true;
2116
- if (config.native === false) return false;
2117
- return false;
2118
- }
2119
- /**
2120
- * Build the callback URL that Mattermost will POST to when a command is invoked.
2121
- */
2122
- function resolveCallbackUrl(params) {
2123
- if (params.config.callbackUrl) return params.config.callbackUrl;
2124
- const isWildcardBindHost = (rawHost) => {
2125
- const trimmed = rawHost.trim();
2126
- if (!trimmed) return false;
2127
- const host = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
2128
- return host === "0.0.0.0" || host === "::" || host === "0:0:0:0:0:0:0:0" || host === "::0";
2129
- };
2130
- let host = params.gatewayHost && !isWildcardBindHost(params.gatewayHost) ? params.gatewayHost : "localhost";
2131
- const path = normalizeCallbackPath(params.config.callbackPath);
2132
- if (host.includes(":") && !(host.startsWith("[") && host.endsWith("]"))) host = `[${host}]`;
2133
- return `http://${host}:${params.gatewayPort}${path}`;
2134
- }
2135
- //#endregion
2136
- //#region extensions/mattermost/src/mattermost/slash-http.ts
2137
- const MAX_BODY_BYTES = 64 * 1024;
2138
- const BODY_READ_TIMEOUT_MS = 5e3;
2139
- /**
2140
- * Read the full request body as a string.
2141
- */
2142
- function readBody(req, maxBytes) {
2143
- return readRequestBodyWithLimit(req, {
2144
- maxBytes,
2145
- timeoutMs: BODY_READ_TIMEOUT_MS
2146
- });
2147
- }
2148
- function sendJsonResponse(res, status, body) {
2149
- res.statusCode = status;
2150
- res.setHeader("Content-Type", "application/json; charset=utf-8");
2151
- res.end(JSON.stringify(body));
2152
- }
2153
- async function authorizeSlashInvocation(params) {
2154
- const { account, cfg, client, commandText, channelId, senderId, senderName, log } = params;
2155
- const core = getMattermostRuntime();
2156
- let channelInfo = null;
2157
- try {
2158
- channelInfo = await fetchMattermostChannel(client, channelId);
2159
- } catch (err) {
2160
- log?.(`mattermost: slash channel lookup failed for ${channelId}: ${String(err)}`);
2161
- }
2162
- if (!channelInfo) return {
2163
- ok: false,
2164
- denyResponse: {
2165
- response_type: "ephemeral",
2166
- text: "Temporary error: unable to determine channel type. Please try again."
2167
- },
2168
- commandAuthorized: false,
2169
- channelInfo: null,
2170
- kind: "channel",
2171
- chatType: "channel",
2172
- channelName: "",
2173
- channelDisplay: "",
2174
- roomLabel: `#${channelId}`
2175
- };
2176
- const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
2177
- cfg,
2178
- surface: "mattermost"
2179
- });
2180
- const hasControlCommand = core.channel.text.hasControlCommand(commandText, cfg);
2181
- const storeAllowFrom = normalizeMattermostAllowList(await core.channel.pairing.readAllowFromStore({
2182
- channel: "mattermost",
2183
- accountId: account.accountId
2184
- }).catch(() => []));
2185
- const decision = authorizeMattermostCommandInvocation({
2186
- account,
2187
- cfg,
2188
- senderId,
2189
- senderName,
2190
- channelId,
2191
- channelInfo,
2192
- storeAllowFrom,
2193
- allowTextCommands,
2194
- hasControlCommand
2195
- });
2196
- if (!decision.ok) {
2197
- if (decision.denyReason === "dm-pairing") {
2198
- const { code } = await core.channel.pairing.upsertPairingRequest({
2199
- channel: "mattermost",
2200
- accountId: account.accountId,
2201
- id: senderId,
2202
- meta: { name: senderName }
2203
- });
2204
- return {
2205
- ...decision,
2206
- denyResponse: {
2207
- response_type: "ephemeral",
2208
- text: core.channel.pairing.buildPairingReply({
2209
- channel: "mattermost",
2210
- idLine: `Your Mattermost user id: ${senderId}`,
2211
- code
2212
- })
2213
- }
2214
- };
2215
- }
2216
- const denyText = decision.denyReason === "unknown-channel" ? "Temporary error: unable to determine channel type. Please try again." : decision.denyReason === "dm-disabled" ? "This bot is not accepting direct messages." : decision.denyReason === "channels-disabled" ? "Slash commands are disabled in channels." : decision.denyReason === "channel-no-allowlist" ? "Slash commands are not configured for this channel (no allowlist)." : "Unauthorized.";
2217
- return {
2218
- ...decision,
2219
- denyResponse: {
2220
- response_type: "ephemeral",
2221
- text: denyText
2222
- }
2223
- };
2224
- }
2225
- return {
2226
- ...decision,
2227
- denyResponse: void 0
2228
- };
2229
- }
2230
- /**
2231
- * Create the HTTP request handler for Mattermost slash command callbacks.
2232
- *
2233
- * This handler is registered as a plugin HTTP route and receives POSTs
2234
- * from the Mattermost server when a user invokes a registered slash command.
2235
- */
2236
- function createSlashCommandHttpHandler(params) {
2237
- const { account, cfg, runtime, commandTokens, triggerMap, log } = params;
2238
- return async (req, res) => {
2239
- if (req.method !== "POST") {
2240
- res.statusCode = 405;
2241
- res.setHeader("Allow", "POST");
2242
- res.end("Method Not Allowed");
2243
- return;
2244
- }
2245
- let body;
2246
- try {
2247
- body = await readBody(req, MAX_BODY_BYTES);
2248
- } catch (error) {
2249
- if (isRequestBodyLimitError(error, "REQUEST_BODY_TIMEOUT")) {
2250
- res.statusCode = 408;
2251
- res.end("Request body timeout");
2252
- return;
2253
- }
2254
- res.statusCode = 413;
2255
- res.end("Payload Too Large");
2256
- return;
2257
- }
2258
- const contentType = req.headers["content-type"] ?? "";
2259
- const payload = parseSlashCommandPayload(body, contentType);
2260
- if (!payload) {
2261
- sendJsonResponse(res, 400, {
2262
- response_type: "ephemeral",
2263
- text: "Invalid slash command payload."
2264
- });
2265
- return;
2266
- }
2267
- if (commandTokens.size === 0 || !commandTokens.has(payload.token)) {
2268
- sendJsonResponse(res, 401, {
2269
- response_type: "ephemeral",
2270
- text: "Unauthorized: invalid command token."
2271
- });
2272
- return;
2273
- }
2274
- const trigger = payload.command.replace(/^\//, "").trim();
2275
- const commandText = resolveCommandText(trigger, payload.text, triggerMap);
2276
- const channelId = payload.channel_id;
2277
- const senderId = payload.user_id;
2278
- const senderName = payload.user_name ?? senderId;
2279
- const client = createMattermostClient({
2280
- baseUrl: account.baseUrl ?? "",
2281
- botToken: account.botToken ?? ""
2282
- });
2283
- const auth = await authorizeSlashInvocation({
2284
- account,
2285
- cfg,
2286
- client,
2287
- commandText,
2288
- channelId,
2289
- senderId,
2290
- senderName,
2291
- log
2292
- });
2293
- if (!auth.ok) {
2294
- sendJsonResponse(res, 200, auth.denyResponse ?? {
2295
- response_type: "ephemeral",
2296
- text: "Unauthorized."
2297
- });
2298
- return;
2299
- }
2300
- log?.(`mattermost: slash command /${trigger} from ${senderName} in ${channelId}`);
2301
- sendJsonResponse(res, 200, {
2302
- response_type: "ephemeral",
2303
- text: "Processing..."
2304
- });
2305
- try {
2306
- await handleSlashCommandAsync({
2307
- account,
2308
- cfg,
2309
- runtime,
2310
- client,
2311
- commandText,
2312
- channelId,
2313
- senderId,
2314
- senderName,
2315
- teamId: payload.team_id,
2316
- triggerId: payload.trigger_id,
2317
- kind: auth.kind,
2318
- chatType: auth.chatType,
2319
- channelName: auth.channelName,
2320
- channelDisplay: auth.channelDisplay,
2321
- roomLabel: auth.roomLabel,
2322
- commandAuthorized: auth.commandAuthorized,
2323
- log
2324
- });
2325
- } catch (err) {
2326
- log?.(`mattermost: slash command handler error: ${String(err)}`);
2327
- try {
2328
- await sendMessageMattermost(`channel:${channelId}`, "Sorry, something went wrong processing that command.", { accountId: account.accountId });
2329
- } catch {}
2330
- }
2331
- };
2332
- }
2333
- async function handleSlashCommandAsync(params) {
2334
- const { account, cfg, runtime, client, commandText, channelId, senderId, senderName, teamId, kind, chatType, channelName, channelDisplay, roomLabel, commandAuthorized, triggerId, log } = params;
2335
- const core = getMattermostRuntime();
2336
- const route = core.channel.routing.resolveAgentRoute({
2337
- cfg,
2338
- channel: "mattermost",
2339
- accountId: account.accountId,
2340
- teamId,
2341
- peer: {
2342
- kind,
2343
- id: kind === "direct" ? senderId : channelId
2344
- }
2345
- });
2346
- const fromLabel = kind === "direct" ? `Mattermost DM from ${senderName}` : `Mattermost message in ${roomLabel} from ${senderName}`;
2347
- const to = kind === "direct" ? `user:${senderId}` : `channel:${channelId}`;
2348
- const pickerEntry = resolveMattermostModelPickerEntry(commandText);
2349
- if (pickerEntry) {
2350
- const data = await buildModelsProviderData(cfg, route.agentId);
2351
- if (data.providers.length === 0) {
2352
- await sendMessageMattermost(to, "No models available.", { accountId: account.accountId });
2353
- return;
2354
- }
2355
- const currentModel = resolveMattermostModelPickerCurrentModel({
2356
- cfg,
2357
- route,
2358
- data
2359
- });
2360
- const view = pickerEntry.kind === "summary" ? renderMattermostModelSummaryView({
2361
- ownerUserId: senderId,
2362
- currentModel
2363
- }) : pickerEntry.kind === "providers" ? renderMattermostProviderPickerView({
2364
- ownerUserId: senderId,
2365
- data,
2366
- currentModel
2367
- }) : renderMattermostModelsPickerView({
2368
- ownerUserId: senderId,
2369
- data,
2370
- provider: pickerEntry.provider,
2371
- page: 1,
2372
- currentModel
2373
- });
2374
- await sendMessageMattermost(to, view.text, {
2375
- accountId: account.accountId,
2376
- buttons: view.buttons
2377
- });
2378
- runtime.log?.(`delivered model picker to ${to}`);
2379
- return;
2380
- }
2381
- const ctxPayload = core.channel.reply.finalizeInboundContext({
2382
- Body: commandText,
2383
- BodyForAgent: commandText,
2384
- RawBody: commandText,
2385
- CommandBody: commandText,
2386
- From: kind === "direct" ? `mattermost:${senderId}` : kind === "group" ? `mattermost:group:${channelId}` : `mattermost:channel:${channelId}`,
2387
- To: to,
2388
- SessionKey: route.sessionKey,
2389
- AccountId: route.accountId,
2390
- ChatType: chatType,
2391
- ConversationLabel: fromLabel,
2392
- GroupSubject: kind !== "direct" ? channelDisplay || roomLabel : void 0,
2393
- SenderName: senderName,
2394
- SenderId: senderId,
2395
- Provider: "mattermost",
2396
- Surface: "mattermost",
2397
- MessageSid: triggerId ?? `slash-${Date.now()}`,
2398
- Timestamp: Date.now(),
2399
- WasMentioned: true,
2400
- CommandAuthorized: commandAuthorized,
2401
- CommandSource: "native",
2402
- OriginatingChannel: "mattermost",
2403
- OriginatingTo: to
2404
- });
2405
- const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "mattermost", account.accountId, { fallbackLimit: account.textChunkLimit ?? 4e3 });
2406
- const tableMode = core.channel.text.resolveMarkdownTableMode({
2407
- cfg,
2408
- channel: "mattermost",
2409
- accountId: account.accountId
2410
- });
2411
- const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
2412
- cfg,
2413
- agentId: route.agentId,
2414
- channel: "mattermost",
2415
- accountId: account.accountId
2416
- });
2417
- const humanDelay = core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId);
2418
- const typingCallbacks = createTypingCallbacks({
2419
- start: () => sendMattermostTyping(client, { channelId }),
2420
- onStartError: (err) => {
2421
- logTypingFailure({
2422
- log: (message) => log?.(message),
2423
- channel: "mattermost",
2424
- target: channelId,
2425
- error: err
2426
- });
2427
- }
2428
- });
2429
- const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
2430
- ...prefixOptions,
2431
- humanDelay,
2432
- deliver: async (payload) => {
2433
- await deliverMattermostReplyPayload({
2434
- core,
2435
- cfg,
2436
- payload,
2437
- to,
2438
- accountId: account.accountId,
2439
- agentId: route.agentId,
2440
- textLimit,
2441
- tableMode,
2442
- sendMessage: sendMessageMattermost
2443
- });
2444
- runtime.log?.(`delivered slash reply to ${to}`);
2445
- },
2446
- onError: (err, info) => {
2447
- runtime.error?.(`mattermost slash ${info.kind} reply failed: ${String(err)}`);
2448
- },
2449
- onReplyStart: typingCallbacks.onReplyStart
2450
- });
2451
- await core.channel.reply.withReplyDispatcher({
2452
- dispatcher,
2453
- onSettled: () => {
2454
- markDispatchIdle();
2455
- },
2456
- run: () => core.channel.reply.dispatchReplyFromConfig({
2457
- ctx: ctxPayload,
2458
- cfg,
2459
- dispatcher,
2460
- replyOptions: {
2461
- ...replyOptions,
2462
- disableBlockStreaming: typeof account.blockStreaming === "boolean" ? !account.blockStreaming : void 0,
2463
- onModelSelected
2464
- }
2465
- })
2466
- });
2467
- }
2468
- //#endregion
2469
- //#region extensions/mattermost/src/mattermost/slash-state.ts
2470
- /** Map from accountId → per-account slash command state. */
2471
- const accountStates = /* @__PURE__ */ new Map();
2472
- function resolveSlashHandlerForToken(token) {
2473
- const matches = [];
2474
- for (const [accountId, state] of accountStates) if (state.commandTokens.has(token) && state.handler) matches.push({
2475
- accountId,
2476
- handler: state.handler
2477
- });
2478
- if (matches.length === 0) return { kind: "none" };
2479
- if (matches.length === 1) return {
2480
- kind: "single",
2481
- handler: matches[0].handler,
2482
- accountIds: [matches[0].accountId]
2483
- };
2484
- return {
2485
- kind: "ambiguous",
2486
- accountIds: matches.map((entry) => entry.accountId)
2487
- };
2488
- }
2489
- /**
2490
- * Get the slash command state for a specific account, or null if not activated.
2491
- */
2492
- function getSlashCommandState(accountId) {
2493
- return accountStates.get(accountId) ?? null;
2494
- }
2495
- /**
2496
- * Activate slash commands for a specific account.
2497
- * Called from the monitor after bot connects.
2498
- */
2499
- function activateSlashCommands(params) {
2500
- const { account, commandTokens, registeredCommands, triggerMap, api, log } = params;
2501
- const accountId = account.accountId;
2502
- const tokenSet = new Set(commandTokens);
2503
- const handler = createSlashCommandHttpHandler({
2504
- account,
2505
- cfg: api.cfg,
2506
- runtime: api.runtime,
2507
- commandTokens: tokenSet,
2508
- triggerMap,
2509
- log
2510
- });
2511
- accountStates.set(accountId, {
2512
- commandTokens: tokenSet,
2513
- registeredCommands,
2514
- handler,
2515
- account,
2516
- triggerMap: triggerMap ?? /* @__PURE__ */ new Map()
2517
- });
2518
- log?.(`mattermost: slash commands activated for account ${accountId} (${registeredCommands.length} commands)`);
2519
- }
2520
- /**
2521
- * Deactivate slash commands for a specific account (on shutdown/disconnect).
2522
- */
2523
- function deactivateSlashCommands(accountId) {
2524
- if (accountId) {
2525
- const state = accountStates.get(accountId);
2526
- if (state) {
2527
- state.commandTokens.clear();
2528
- state.registeredCommands = [];
2529
- state.handler = null;
2530
- accountStates.delete(accountId);
2531
- }
2532
- } else {
2533
- for (const [, state] of accountStates) {
2534
- state.commandTokens.clear();
2535
- state.registeredCommands = [];
2536
- state.handler = null;
2537
- }
2538
- accountStates.clear();
2539
- }
2540
- }
2541
- /**
2542
- * Register the HTTP route for slash command callbacks.
2543
- * Called during plugin registration.
2544
- *
2545
- * The single HTTP route dispatches to the correct per-account handler
2546
- * by matching the inbound token against each account's registered tokens.
2547
- */
2548
- function registerSlashCommandRoute(api) {
2549
- const mmConfig = api.config.channels?.mattermost;
2550
- const callbackPaths = /* @__PURE__ */ new Set();
2551
- const addCallbackPaths = (raw) => {
2552
- const resolved = resolveSlashCommandConfig(raw);
2553
- callbackPaths.add(resolved.callbackPath);
2554
- if (resolved.callbackUrl) try {
2555
- const urlPath = new URL(resolved.callbackUrl).pathname;
2556
- if (urlPath && urlPath !== resolved.callbackPath) callbackPaths.add(urlPath);
2557
- } catch {}
2558
- };
2559
- const commandsRaw = mmConfig?.commands;
2560
- addCallbackPaths(commandsRaw);
2561
- const accountsRaw = mmConfig?.accounts ?? {};
2562
- for (const accountId of Object.keys(accountsRaw)) {
2563
- const accountCommandsRaw = accountsRaw[accountId]?.commands;
2564
- addCallbackPaths(accountCommandsRaw);
2565
- }
2566
- const routeHandler = async (req, res) => {
2567
- if (accountStates.size === 0) {
2568
- res.statusCode = 503;
2569
- res.setHeader("Content-Type", "application/json; charset=utf-8");
2570
- res.end(JSON.stringify({
2571
- response_type: "ephemeral",
2572
- text: "Slash commands are not yet initialized. Please try again in a moment."
2573
- }));
2574
- return;
2575
- }
2576
- if (accountStates.size === 1) {
2577
- const [, state] = [...accountStates.entries()][0];
2578
- if (!state.handler) {
2579
- res.statusCode = 503;
2580
- res.setHeader("Content-Type", "application/json; charset=utf-8");
2581
- res.end(JSON.stringify({
2582
- response_type: "ephemeral",
2583
- text: "Slash commands are not yet initialized. Please try again in a moment."
2584
- }));
2585
- return;
2586
- }
2587
- await state.handler(req, res);
2588
- return;
2589
- }
2590
- const chunks = [];
2591
- const MAX_BODY = 64 * 1024;
2592
- let size = 0;
2593
- for await (const chunk of req) {
2594
- size += chunk.length;
2595
- if (size > MAX_BODY) {
2596
- res.statusCode = 413;
2597
- res.end("Payload Too Large");
2598
- return;
2599
- }
2600
- chunks.push(chunk);
2601
- }
2602
- const bodyStr = Buffer.concat(chunks).toString("utf8");
2603
- let token = null;
2604
- const ct = req.headers["content-type"] ?? "";
2605
- try {
2606
- if (ct.includes("application/json")) token = JSON.parse(bodyStr).token ?? null;
2607
- else token = new URLSearchParams(bodyStr).get("token");
2608
- } catch {}
2609
- const match = token ? resolveSlashHandlerForToken(token) : { kind: "none" };
2610
- if (match.kind === "none") {
2611
- res.statusCode = 401;
2612
- res.setHeader("Content-Type", "application/json; charset=utf-8");
2613
- res.end(JSON.stringify({
2614
- response_type: "ephemeral",
2615
- text: "Unauthorized: invalid command token."
2616
- }));
2617
- return;
2618
- }
2619
- if (match.kind === "ambiguous") {
2620
- api.logger.warn?.(`mattermost: slash callback token matched multiple accounts (${match.accountIds?.join(", ")})`);
2621
- res.statusCode = 409;
2622
- res.setHeader("Content-Type", "application/json; charset=utf-8");
2623
- res.end(JSON.stringify({
2624
- response_type: "ephemeral",
2625
- text: "Conflict: command token is not unique across accounts."
2626
- }));
2627
- return;
2628
- }
2629
- const matchedHandler = match.handler;
2630
- const { Readable } = await import("node:stream");
2631
- const syntheticReq = new Readable({ read() {
2632
- this.push(Buffer.from(bodyStr, "utf8"));
2633
- this.push(null);
2634
- } });
2635
- syntheticReq.method = req.method;
2636
- syntheticReq.url = req.url;
2637
- syntheticReq.headers = req.headers;
2638
- await matchedHandler(syntheticReq, res);
2639
- };
2640
- for (const callbackPath of callbackPaths) {
2641
- api.registerHttpRoute({
2642
- path: callbackPath,
2643
- auth: "plugin",
2644
- handler: routeHandler
2645
- });
2646
- api.logger.info?.(`mattermost: registered slash command callback at ${callbackPath}`);
2647
- }
2648
- }
2649
- //#endregion
2650
- //#region extensions/mattermost/src/mattermost/monitor.ts
2651
- const RECENT_MATTERMOST_MESSAGE_TTL_MS = 5 * 6e4;
2652
- const RECENT_MATTERMOST_MESSAGE_MAX = 2e3;
2653
- const CHANNEL_CACHE_TTL_MS = 5 * 6e4;
2654
- const USER_CACHE_TTL_MS = 10 * 6e4;
2655
- function isLoopbackHost(hostname) {
2656
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
2657
- }
2658
- function normalizeInteractionSourceIps(values) {
2659
- return (values ?? []).map((value) => value.trim()).filter(Boolean);
2660
- }
2661
- const recentInboundMessages = createDedupeCache({
2662
- ttlMs: RECENT_MATTERMOST_MESSAGE_TTL_MS,
2663
- maxSize: RECENT_MATTERMOST_MESSAGE_MAX
2664
- });
2665
- function resolveRuntime(opts) {
2666
- return opts.runtime ?? {
2667
- log: console.log,
2668
- error: console.error,
2669
- exit: (code) => {
2670
- throw new Error(`exit ${code}`);
2671
- }
2672
- };
2673
- }
2674
- function isSystemPost(post) {
2675
- const type = post.type?.trim();
2676
- return Boolean(type);
2677
- }
2678
- function mapMattermostChannelTypeToChatType(channelType) {
2679
- if (!channelType) return "channel";
2680
- const normalized = channelType.trim().toUpperCase();
2681
- if (normalized === "D") return "direct";
2682
- if (normalized === "G") return "group";
2683
- if (normalized === "P") return "group";
2684
- return "channel";
2685
- }
2686
- function channelChatType(kind) {
2687
- if (kind === "direct") return "direct";
2688
- if (kind === "group") return "group";
2689
- return "channel";
2690
- }
2691
- function evaluateMattermostMentionGate(params) {
2692
- const shouldRequireMention = params.kind !== "direct" && params.resolveRequireMention({
2693
- cfg: params.cfg,
2694
- channel: "mattermost",
2695
- accountId: params.accountId,
2696
- groupId: params.channelId,
2697
- requireMentionOverride: params.requireMentionOverride
2698
- });
2699
- const shouldBypassMention = params.isControlCommand && shouldRequireMention && !params.wasMentioned && params.commandAuthorized;
2700
- const effectiveWasMentioned = params.wasMentioned || shouldBypassMention || params.oncharTriggered;
2701
- if (params.oncharEnabled && !params.oncharTriggered && !params.wasMentioned && !params.isControlCommand) return {
2702
- shouldRequireMention,
2703
- shouldBypassMention,
2704
- effectiveWasMentioned,
2705
- dropReason: "onchar-not-triggered"
2706
- };
2707
- if (params.kind !== "direct" && shouldRequireMention && params.canDetectMention && !effectiveWasMentioned) return {
2708
- shouldRequireMention,
2709
- shouldBypassMention,
2710
- effectiveWasMentioned,
2711
- dropReason: "missing-mention"
2712
- };
2713
- return {
2714
- shouldRequireMention,
2715
- shouldBypassMention,
2716
- effectiveWasMentioned,
2717
- dropReason: null
2718
- };
2719
- }
2720
- function resolveMattermostReplyRootId(params) {
2721
- const threadRootId = params.threadRootId?.trim();
2722
- if (threadRootId) return threadRootId;
2723
- return params.replyToId?.trim() || void 0;
2724
- }
2725
- function resolveMattermostEffectiveReplyToId(params) {
2726
- const threadRootId = params.threadRootId?.trim();
2727
- if (threadRootId) return threadRootId;
2728
- if (params.kind === "direct") return;
2729
- const postId = params.postId?.trim();
2730
- if (!postId) return;
2731
- return params.replyToMode === "all" || params.replyToMode === "first" ? postId : void 0;
2732
- }
2733
- function resolveMattermostThreadSessionContext(params) {
2734
- const effectiveReplyToId = resolveMattermostEffectiveReplyToId({
2735
- kind: params.kind,
2736
- postId: params.postId,
2737
- replyToMode: params.replyToMode,
2738
- threadRootId: params.threadRootId
2739
- });
2740
- const threadKeys = resolveThreadSessionKeys({
2741
- baseSessionKey: params.baseSessionKey,
2742
- threadId: effectiveReplyToId,
2743
- parentSessionKey: effectiveReplyToId ? params.baseSessionKey : void 0
2744
- });
2745
- return {
2746
- effectiveReplyToId,
2747
- sessionKey: threadKeys.sessionKey,
2748
- parentSessionKey: threadKeys.parentSessionKey
2749
- };
2750
- }
2751
- function buildMattermostAttachmentPlaceholder(mediaList) {
2752
- if (mediaList.length === 0) return "";
2753
- if (mediaList.length === 1) return `<media:${mediaList[0].kind === "unknown" ? "document" : mediaList[0].kind}>`;
2754
- const allImages = mediaList.every((media) => media.kind === "image");
2755
- const label = allImages ? "image" : "file";
2756
- const suffix = mediaList.length === 1 ? label : `${label}s`;
2757
- return `${allImages ? "<media:image>" : "<media:document>"} (${mediaList.length} ${suffix})`;
2758
- }
2759
- function buildMattermostWsUrl(baseUrl) {
2760
- const normalized = normalizeMattermostBaseUrl(baseUrl);
2761
- if (!normalized) throw new Error("Mattermost baseUrl is required");
2762
- return `${normalized.replace(/^http/i, "ws")}/api/v4/websocket`;
2763
- }
2764
- async function monitorMattermostProvider(opts = {}) {
2765
- const core = getMattermostRuntime();
2766
- const runtime = resolveRuntime(opts);
2767
- const cfg = opts.config ?? core.config.loadConfig();
2768
- const account = resolveMattermostAccount({
2769
- cfg,
2770
- accountId: opts.accountId
2771
- });
2772
- const pairing = createScopedPairingAccess({
2773
- core,
2774
- channel: "mattermost",
2775
- accountId: account.accountId
2776
- });
2777
- const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
2778
- const botToken = opts.botToken?.trim() || account.botToken?.trim();
2779
- if (!botToken) throw new Error(`Mattermost bot token missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.botToken or MATTERMOST_BOT_TOKEN for default).`);
2780
- const baseUrl = normalizeMattermostBaseUrl(opts.baseUrl ?? account.baseUrl);
2781
- if (!baseUrl) throw new Error(`Mattermost baseUrl missing for account "${account.accountId}" (set channels.mattermost.accounts.${account.accountId}.baseUrl or MATTERMOST_URL for default).`);
2782
- const client = createMattermostClient({
2783
- baseUrl,
2784
- botToken
2785
- });
2786
- const botUser = await fetchMattermostMe(client);
2787
- const botUserId = botUser.id;
2788
- const botUsername = botUser.username?.trim() || void 0;
2789
- runtime.log?.(`mattermost connected as ${botUsername ? `@${botUsername}` : botUserId}`);
2790
- const commandsRaw = account.config.commands;
2791
- const slashConfig = resolveSlashCommandConfig(commandsRaw);
2792
- const slashEnabled = isSlashCommandsEnabled(slashConfig);
2793
- if (slashEnabled) try {
2794
- const teams = await fetchMattermostUserTeams(client, botUserId);
2795
- const envPortRaw = process.env.MOLDCLAW_GATEWAY_PORT?.trim();
2796
- const slashCallbackUrl = resolveCallbackUrl({
2797
- config: slashConfig,
2798
- gatewayPort: parseStrictPositiveInteger(envPortRaw) ?? cfg.gateway?.port ?? 18789,
2799
- gatewayHost: cfg.gateway?.customBindHost ?? void 0
2800
- });
2801
- try {
2802
- const mmHost = new URL(baseUrl).hostname;
2803
- const callbackHost = new URL(slashCallbackUrl).hostname;
2804
- if (isLoopbackHost(callbackHost) && !isLoopbackHost(mmHost)) runtime.error?.(`mattermost: slash commands callbackUrl resolved to ${slashCallbackUrl} (loopback) while baseUrl is ${baseUrl}. This MAY be unreachable depending on your deployment. If native slash commands don't work, set channels.mattermost.commands.callbackUrl to a URL reachable from the Mattermost server (e.g. your public reverse proxy URL).`);
2805
- } catch {}
2806
- const commandsToRegister = [...DEFAULT_COMMAND_SPECS];
2807
- if (slashConfig.nativeSkills === true) try {
2808
- const skillCommands = listSkillCommandsForAgents({ cfg });
2809
- for (const spec of skillCommands) {
2810
- const name = typeof spec.name === "string" ? spec.name.trim() : "";
2811
- if (!name) continue;
2812
- const trigger = name.startsWith("oc_") ? name : `oc_${name}`;
2813
- commandsToRegister.push({
2814
- trigger,
2815
- description: spec.description || `Run skill ${name}`,
2816
- autoComplete: true,
2817
- autoCompleteHint: "[args]",
2818
- originalName: name
2819
- });
2820
- }
2821
- } catch (err) {
2822
- runtime.error?.(`mattermost: failed to list skill commands: ${String(err)}`);
2823
- }
2824
- const seen = /* @__PURE__ */ new Set();
2825
- const dedupedCommands = commandsToRegister.filter((cmd) => {
2826
- const key = cmd.trigger.trim();
2827
- if (!key) return false;
2828
- if (seen.has(key)) return false;
2829
- seen.add(key);
2830
- return true;
2831
- });
2832
- const allRegistered = [];
2833
- let teamRegistrationFailures = 0;
2834
- for (const team of teams) try {
2835
- const registered = await registerSlashCommands({
2836
- client,
2837
- teamId: team.id,
2838
- creatorUserId: botUserId,
2839
- callbackUrl: slashCallbackUrl,
2840
- commands: dedupedCommands,
2841
- log: (msg) => runtime.log?.(msg)
2842
- });
2843
- allRegistered.push(...registered);
2844
- } catch (err) {
2845
- teamRegistrationFailures += 1;
2846
- runtime.error?.(`mattermost: failed to register slash commands for team ${team.id}: ${String(err)}`);
2847
- }
2848
- if (allRegistered.length === 0) runtime.error?.("mattermost: native slash commands enabled but no commands could be registered; keeping slash callbacks inactive");
2849
- else {
2850
- if (teamRegistrationFailures > 0) runtime.error?.(`mattermost: slash command registration completed with ${teamRegistrationFailures} team error(s)`);
2851
- const triggerMap = /* @__PURE__ */ new Map();
2852
- for (const cmd of dedupedCommands) if (cmd.originalName) triggerMap.set(cmd.trigger, cmd.originalName);
2853
- activateSlashCommands({
2854
- account,
2855
- commandTokens: allRegistered.map((cmd) => cmd.token).filter(Boolean),
2856
- registeredCommands: allRegistered,
2857
- triggerMap,
2858
- api: {
2859
- cfg,
2860
- runtime
2861
- },
2862
- log: (msg) => runtime.log?.(msg)
2863
- });
2864
- runtime.log?.(`mattermost: slash commands registered (${allRegistered.length} commands across ${teams.length} teams, callback=${slashCallbackUrl})`);
2865
- }
2866
- } catch (err) {
2867
- runtime.error?.(`mattermost: failed to register slash commands: ${String(err)}`);
2868
- }
2869
- setInteractionSecret(account.accountId, botToken);
2870
- const interactionPath = resolveInteractionCallbackPath(account.accountId);
2871
- const callbackUrl = computeInteractionCallbackUrl(account.accountId, {
2872
- gateway: cfg.gateway,
2873
- interactions: account.config.interactions
2874
- });
2875
- setInteractionCallbackUrl(account.accountId, callbackUrl);
2876
- const allowedInteractionSourceIps = normalizeInteractionSourceIps(account.config.interactions?.allowedSourceIps);
2877
- try {
2878
- const mmHost = new URL(baseUrl).hostname;
2879
- const callbackHost = new URL(callbackUrl).hostname;
2880
- if (isLoopbackHost(callbackHost) && !isLoopbackHost(mmHost)) runtime.error?.(`mattermost: interactions callbackUrl resolved to ${callbackUrl} (loopback) while baseUrl is ${baseUrl}. This MAY be unreachable depending on your deployment. If button clicks don't work, set channels.mattermost.interactions.callbackBaseUrl to a URL reachable from the Mattermost server (e.g. your public reverse proxy URL).`);
2881
- if (!isLoopbackHost(callbackHost) && allowedInteractionSourceIps.length === 0) runtime.error?.(`mattermost: interactions callbackUrl resolved to ${callbackUrl} without channels.mattermost.interactions.allowedSourceIps. For safety, non-loopback callback sources will be rejected until you allowlist the Mattermost server or trusted ingress IPs.`);
2882
- } catch {}
2883
- const effectiveInteractionSourceIps = allowedInteractionSourceIps.length > 0 ? allowedInteractionSourceIps : ["127.0.0.1", "::1"];
2884
- const unregisterInteractions = registerPluginHttpRoute({
2885
- path: interactionPath,
2886
- fallbackPath: "/mattermost/interactions/default",
2887
- auth: "plugin",
2888
- handler: createMattermostInteractionHandler({
2889
- client,
2890
- botUserId,
2891
- accountId: account.accountId,
2892
- allowedSourceIps: effectiveInteractionSourceIps,
2893
- trustedProxies: cfg.gateway?.trustedProxies,
2894
- allowRealIpFallback: cfg.gateway?.allowRealIpFallback === true,
2895
- handleInteraction: handleModelPickerInteraction,
2896
- authorizeButtonClick: async ({ payload, post }) => {
2897
- const channelInfo = await resolveChannelInfo(payload.channel_id);
2898
- const isDirect = channelInfo?.type?.trim().toUpperCase() === "D";
2899
- const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
2900
- cfg,
2901
- surface: "mattermost"
2902
- });
2903
- const decision = authorizeMattermostCommandInvocation({
2904
- account,
2905
- cfg,
2906
- senderId: payload.user_id,
2907
- senderName: payload.user_name ?? "",
2908
- channelId: payload.channel_id,
2909
- channelInfo,
2910
- storeAllowFrom: isDirect ? await readStoreAllowFromForDmPolicy({
2911
- provider: "mattermost",
2912
- accountId: account.accountId,
2913
- dmPolicy: account.config.dmPolicy ?? "pairing",
2914
- readStore: pairing.readStoreForDmPolicy
2915
- }) : void 0,
2916
- allowTextCommands,
2917
- hasControlCommand: false
2918
- });
2919
- if (decision.ok) return { ok: true };
2920
- return {
2921
- ok: false,
2922
- response: {
2923
- update: {
2924
- message: post.message ?? "",
2925
- props: post.props
2926
- },
2927
- ephemeral_text: `MoldClaw ignored this action for ${decision.roomLabel}.`
2928
- }
2929
- };
2930
- },
2931
- resolveSessionKey: async ({ channelId, userId, post }) => {
2932
- const channelInfo = await resolveChannelInfo(channelId);
2933
- const kind = mapMattermostChannelTypeToChatType(channelInfo?.type);
2934
- const teamId = channelInfo?.team_id ?? void 0;
2935
- const route = core.channel.routing.resolveAgentRoute({
2936
- cfg,
2937
- channel: "mattermost",
2938
- accountId: account.accountId,
2939
- teamId,
2940
- peer: {
2941
- kind,
2942
- id: kind === "direct" ? userId : channelId
2943
- }
2944
- });
2945
- const replyToMode = resolveMattermostReplyToMode(account, kind);
2946
- return resolveMattermostThreadSessionContext({
2947
- baseSessionKey: route.sessionKey,
2948
- kind,
2949
- postId: post.id || void 0,
2950
- replyToMode,
2951
- threadRootId: post.root_id
2952
- }).sessionKey;
2953
- },
2954
- dispatchButtonClick: async (opts) => {
2955
- const channelInfo = await resolveChannelInfo(opts.channelId);
2956
- const kind = mapMattermostChannelTypeToChatType(channelInfo?.type);
2957
- const chatType = channelChatType(kind);
2958
- const teamId = channelInfo?.team_id ?? void 0;
2959
- const channelName = channelInfo?.name ?? void 0;
2960
- const channelDisplay = channelInfo?.display_name ?? channelName ?? opts.channelId;
2961
- const route = core.channel.routing.resolveAgentRoute({
2962
- cfg,
2963
- channel: "mattermost",
2964
- accountId: account.accountId,
2965
- teamId,
2966
- peer: {
2967
- kind,
2968
- id: kind === "direct" ? opts.userId : opts.channelId
2969
- }
2970
- });
2971
- const replyToMode = resolveMattermostReplyToMode(account, kind);
2972
- const threadContext = resolveMattermostThreadSessionContext({
2973
- baseSessionKey: route.sessionKey,
2974
- kind,
2975
- postId: opts.post.id || opts.postId,
2976
- replyToMode,
2977
- threadRootId: opts.post.root_id
2978
- });
2979
- const to = kind === "direct" ? `user:${opts.userId}` : `channel:${opts.channelId}`;
2980
- const bodyText = `[Button click: user @${opts.userName} selected "${opts.actionName}"]`;
2981
- const ctxPayload = core.channel.reply.finalizeInboundContext({
2982
- Body: bodyText,
2983
- BodyForAgent: bodyText,
2984
- RawBody: bodyText,
2985
- CommandBody: bodyText,
2986
- From: kind === "direct" ? `mattermost:${opts.userId}` : kind === "group" ? `mattermost:group:${opts.channelId}` : `mattermost:channel:${opts.channelId}`,
2987
- To: to,
2988
- SessionKey: threadContext.sessionKey,
2989
- ParentSessionKey: threadContext.parentSessionKey,
2990
- AccountId: route.accountId,
2991
- ChatType: chatType,
2992
- ConversationLabel: `mattermost:${opts.userName}`,
2993
- GroupSubject: kind !== "direct" ? channelDisplay : void 0,
2994
- GroupChannel: channelName ? `#${channelName}` : void 0,
2995
- GroupSpace: teamId,
2996
- SenderName: opts.userName,
2997
- SenderId: opts.userId,
2998
- Provider: "mattermost",
2999
- Surface: "mattermost",
3000
- MessageSid: `interaction:${opts.postId}:${opts.actionId}`,
3001
- ReplyToId: threadContext.effectiveReplyToId,
3002
- MessageThreadId: threadContext.effectiveReplyToId,
3003
- WasMentioned: true,
3004
- CommandAuthorized: false,
3005
- OriginatingChannel: "mattermost",
3006
- OriginatingTo: to
3007
- });
3008
- const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "mattermost", account.accountId, { fallbackLimit: account.textChunkLimit ?? 4e3 });
3009
- const tableMode = core.channel.text.resolveMarkdownTableMode({
3010
- cfg,
3011
- channel: "mattermost",
3012
- accountId: account.accountId
3013
- });
3014
- const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
3015
- cfg,
3016
- agentId: route.agentId,
3017
- channel: "mattermost",
3018
- accountId: account.accountId
3019
- });
3020
- const typingCallbacks = createTypingCallbacks({
3021
- start: () => sendTypingIndicator(opts.channelId, threadContext.effectiveReplyToId),
3022
- onStartError: (err) => {
3023
- logTypingFailure({
3024
- log: (message) => logger.debug?.(message),
3025
- channel: "mattermost",
3026
- target: opts.channelId,
3027
- error: err
3028
- });
3029
- }
3030
- });
3031
- const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
3032
- ...prefixOptions,
3033
- humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
3034
- deliver: async (payload) => {
3035
- await deliverMattermostReplyPayload({
3036
- core,
3037
- cfg,
3038
- payload,
3039
- to,
3040
- accountId: account.accountId,
3041
- agentId: route.agentId,
3042
- replyToId: resolveMattermostReplyRootId({
3043
- threadRootId: threadContext.effectiveReplyToId,
3044
- replyToId: payload.replyToId
3045
- }),
3046
- textLimit,
3047
- tableMode,
3048
- sendMessage: sendMessageMattermost
3049
- });
3050
- runtime.log?.(`delivered button-click reply to ${to}`);
3051
- },
3052
- onError: (err, info) => {
3053
- runtime.error?.(`mattermost button-click ${info.kind} reply failed: ${String(err)}`);
3054
- },
3055
- onReplyStart: typingCallbacks.onReplyStart
3056
- });
3057
- await core.channel.reply.dispatchReplyFromConfig({
3058
- ctx: ctxPayload,
3059
- cfg,
3060
- dispatcher,
3061
- replyOptions: {
3062
- ...replyOptions,
3063
- disableBlockStreaming: typeof account.blockStreaming === "boolean" ? !account.blockStreaming : void 0,
3064
- onModelSelected
3065
- }
3066
- });
3067
- markDispatchIdle();
3068
- },
3069
- log: (msg) => runtime.log?.(msg)
3070
- }),
3071
- pluginId: "mattermost",
3072
- source: "mattermost-interactions",
3073
- accountId: account.accountId,
3074
- log: (msg) => runtime.log?.(msg)
3075
- });
3076
- const channelCache = /* @__PURE__ */ new Map();
3077
- const userCache = /* @__PURE__ */ new Map();
3078
- const logger = core.logging.getChildLogger({ module: "mattermost" });
3079
- const logVerboseMessage = (message) => {
3080
- if (!core.logging.shouldLogVerbose()) return;
3081
- logger.debug?.(message);
3082
- };
3083
- const mediaMaxBytes = resolveChannelMediaMaxBytes({
3084
- cfg,
3085
- resolveChannelLimitMb: () => void 0,
3086
- accountId: account.accountId
3087
- }) ?? 8 * 1024 * 1024;
3088
- const historyLimit = Math.max(0, cfg.messages?.groupChat?.historyLimit ?? 50);
3089
- const channelHistories = /* @__PURE__ */ new Map();
3090
- const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
3091
- const { groupPolicy, providerMissingFallbackApplied } = resolveAllowlistProviderRuntimeGroupPolicy({
3092
- providerConfigPresent: cfg.channels?.mattermost !== void 0,
3093
- groupPolicy: account.config.groupPolicy,
3094
- defaultGroupPolicy
3095
- });
3096
- warnMissingProviderGroupPolicyFallbackOnce({
3097
- providerMissingFallbackApplied,
3098
- providerKey: "mattermost",
3099
- accountId: account.accountId,
3100
- log: (message) => logVerboseMessage(message)
3101
- });
3102
- const resolveMattermostMedia = async (fileIds) => {
3103
- const ids = (fileIds ?? []).map((id) => id?.trim()).filter(Boolean);
3104
- if (ids.length === 0) return [];
3105
- const out = [];
3106
- for (const fileId of ids) try {
3107
- const fetched = await core.channel.media.fetchRemoteMedia({
3108
- url: `${client.apiBaseUrl}/files/${fileId}`,
3109
- requestInit: { headers: { Authorization: `Bearer ${client.token}` } },
3110
- filePathHint: fileId,
3111
- maxBytes: mediaMaxBytes,
3112
- ssrfPolicy: { allowedHostnames: [new URL(client.baseUrl).hostname] }
3113
- });
3114
- const saved = await core.channel.media.saveMediaBuffer(fetched.buffer, fetched.contentType ?? void 0, "inbound", mediaMaxBytes);
3115
- const contentType = saved.contentType ?? fetched.contentType ?? void 0;
3116
- out.push({
3117
- path: saved.path,
3118
- contentType,
3119
- kind: core.media.mediaKindFromMime(contentType) ?? "unknown"
3120
- });
3121
- } catch (err) {
3122
- logger.debug?.(`mattermost: failed to download file ${fileId}: ${String(err)}`);
3123
- }
3124
- return out;
3125
- };
3126
- const sendTypingIndicator = async (channelId, parentId) => {
3127
- await sendMattermostTyping(client, {
3128
- channelId,
3129
- parentId
3130
- });
3131
- };
3132
- const resolveChannelInfo = async (channelId) => {
3133
- const cached = channelCache.get(channelId);
3134
- if (cached && cached.expiresAt > Date.now()) return cached.value;
3135
- try {
3136
- const info = await fetchMattermostChannel(client, channelId);
3137
- channelCache.set(channelId, {
3138
- value: info,
3139
- expiresAt: Date.now() + CHANNEL_CACHE_TTL_MS
3140
- });
3141
- return info;
3142
- } catch (err) {
3143
- logger.debug?.(`mattermost: channel lookup failed: ${String(err)}`);
3144
- channelCache.set(channelId, {
3145
- value: null,
3146
- expiresAt: Date.now() + CHANNEL_CACHE_TTL_MS
3147
- });
3148
- return null;
3149
- }
3150
- };
3151
- const resolveUserInfo = async (userId) => {
3152
- const cached = userCache.get(userId);
3153
- if (cached && cached.expiresAt > Date.now()) return cached.value;
3154
- try {
3155
- const info = await fetchMattermostUser(client, userId);
3156
- userCache.set(userId, {
3157
- value: info,
3158
- expiresAt: Date.now() + USER_CACHE_TTL_MS
3159
- });
3160
- return info;
3161
- } catch (err) {
3162
- logger.debug?.(`mattermost: user lookup failed: ${String(err)}`);
3163
- userCache.set(userId, {
3164
- value: null,
3165
- expiresAt: Date.now() + USER_CACHE_TTL_MS
3166
- });
3167
- return null;
3168
- }
3169
- };
3170
- const buildModelPickerProps = (channelId, buttons) => buildButtonProps({
3171
- callbackUrl,
3172
- accountId: account.accountId,
3173
- channelId,
3174
- buttons
3175
- });
3176
- const updateModelPickerPost = async (params) => {
3177
- const props = buildModelPickerProps(params.channelId, params.buttons ?? []) ?? { attachments: [] };
3178
- await updateMattermostPost(client, params.postId, {
3179
- message: params.message,
3180
- props
3181
- });
3182
- return {};
3183
- };
3184
- const runModelPickerCommand = async (params) => {
3185
- const to = params.kind === "direct" ? `user:${params.senderId}` : `channel:${params.channelId}`;
3186
- const fromLabel = params.kind === "direct" ? `Mattermost DM from ${params.senderName}` : `Mattermost message in ${params.roomLabel} from ${params.senderName}`;
3187
- const ctxPayload = core.channel.reply.finalizeInboundContext({
3188
- Body: params.commandText,
3189
- BodyForAgent: params.commandText,
3190
- RawBody: params.commandText,
3191
- CommandBody: params.commandText,
3192
- From: params.kind === "direct" ? `mattermost:${params.senderId}` : params.kind === "group" ? `mattermost:group:${params.channelId}` : `mattermost:channel:${params.channelId}`,
3193
- To: to,
3194
- SessionKey: params.sessionKey,
3195
- ParentSessionKey: params.parentSessionKey,
3196
- AccountId: params.route.accountId,
3197
- ChatType: params.chatType,
3198
- ConversationLabel: fromLabel,
3199
- GroupSubject: params.kind !== "direct" ? params.channelDisplay || params.roomLabel : void 0,
3200
- GroupChannel: params.channelName ? `#${params.channelName}` : void 0,
3201
- GroupSpace: params.teamId,
3202
- SenderName: params.senderName,
3203
- SenderId: params.senderId,
3204
- Provider: "mattermost",
3205
- Surface: "mattermost",
3206
- MessageSid: `interaction:${params.postId}:${Date.now()}`,
3207
- ReplyToId: params.effectiveReplyToId,
3208
- MessageThreadId: params.effectiveReplyToId,
3209
- Timestamp: Date.now(),
3210
- WasMentioned: true,
3211
- CommandAuthorized: params.commandAuthorized,
3212
- CommandSource: "native",
3213
- OriginatingChannel: "mattermost",
3214
- OriginatingTo: to
3215
- });
3216
- const tableMode = core.channel.text.resolveMarkdownTableMode({
3217
- cfg,
3218
- channel: "mattermost",
3219
- accountId: account.accountId
3220
- });
3221
- const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "mattermost", account.accountId, { fallbackLimit: account.textChunkLimit ?? 4e3 });
3222
- const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
3223
- cfg,
3224
- agentId: params.route.agentId,
3225
- channel: "mattermost",
3226
- accountId: account.accountId
3227
- });
3228
- const shouldDeliverReplies = params.deliverReplies === true;
3229
- const capturedTexts = [];
3230
- const typingCallbacks = shouldDeliverReplies ? createTypingCallbacks({
3231
- start: () => sendTypingIndicator(params.channelId, params.effectiveReplyToId),
3232
- onStartError: (err) => {
3233
- logTypingFailure({
3234
- log: (message) => logger.debug?.(message),
3235
- channel: "mattermost",
3236
- target: params.channelId,
3237
- error: err
3238
- });
3239
- }
3240
- }) : void 0;
3241
- const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
3242
- ...prefixOptions,
3243
- deliver: async (payload) => {
3244
- const trimmedPayload = {
3245
- ...payload,
3246
- text: core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode).trim()
3247
- };
3248
- if (!shouldDeliverReplies) {
3249
- if (trimmedPayload.text) capturedTexts.push(trimmedPayload.text);
3250
- return;
3251
- }
3252
- await deliverMattermostReplyPayload({
3253
- core,
3254
- cfg,
3255
- payload: trimmedPayload,
3256
- to,
3257
- accountId: account.accountId,
3258
- agentId: params.route.agentId,
3259
- replyToId: resolveMattermostReplyRootId({
3260
- threadRootId: params.effectiveReplyToId,
3261
- replyToId: trimmedPayload.replyToId
3262
- }),
3263
- textLimit,
3264
- tableMode: "off",
3265
- sendMessage: sendMessageMattermost
3266
- });
3267
- },
3268
- onError: (err, info) => {
3269
- runtime.error?.(`mattermost model picker ${info.kind} reply failed: ${String(err)}`);
3270
- },
3271
- onReplyStart: typingCallbacks?.onReplyStart
3272
- });
3273
- await core.channel.reply.withReplyDispatcher({
3274
- dispatcher,
3275
- onSettled: () => {
3276
- markDispatchIdle();
3277
- },
3278
- run: () => core.channel.reply.dispatchReplyFromConfig({
3279
- ctx: ctxPayload,
3280
- cfg,
3281
- dispatcher,
3282
- replyOptions: {
3283
- ...replyOptions,
3284
- disableBlockStreaming: typeof account.blockStreaming === "boolean" ? !account.blockStreaming : void 0,
3285
- onModelSelected
3286
- }
3287
- })
3288
- });
3289
- return capturedTexts.join("\n\n").trim();
3290
- };
3291
- async function handleModelPickerInteraction(params) {
3292
- const pickerState = parseMattermostModelPickerContext(params.context);
3293
- if (!pickerState) return null;
3294
- if (pickerState.ownerUserId !== params.payload.user_id) return { ephemeral_text: "Only the person who opened this picker can use it." };
3295
- const channelInfo = await resolveChannelInfo(params.payload.channel_id);
3296
- const pickerCommandText = pickerState.action === "select" ? `/model ${pickerState.provider}/${pickerState.model}` : pickerState.action === "list" ? `/models ${pickerState.provider}` : "/models";
3297
- const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
3298
- cfg,
3299
- surface: "mattermost"
3300
- });
3301
- const hasControlCommand = core.channel.text.hasControlCommand(pickerCommandText, cfg);
3302
- const dmPolicy = account.config.dmPolicy ?? "pairing";
3303
- const storeAllowFrom = normalizeMattermostAllowList(await readStoreAllowFromForDmPolicy({
3304
- provider: "mattermost",
3305
- accountId: account.accountId,
3306
- dmPolicy,
3307
- readStore: pairing.readStoreForDmPolicy
3308
- }));
3309
- const auth = authorizeMattermostCommandInvocation({
3310
- account,
3311
- cfg,
3312
- senderId: params.payload.user_id,
3313
- senderName: params.userName,
3314
- channelId: params.payload.channel_id,
3315
- channelInfo,
3316
- storeAllowFrom,
3317
- allowTextCommands,
3318
- hasControlCommand
3319
- });
3320
- if (!auth.ok) {
3321
- if (auth.denyReason === "dm-pairing") {
3322
- const { code } = await pairing.upsertPairingRequest({
3323
- id: params.payload.user_id,
3324
- meta: { name: params.userName }
3325
- });
3326
- return { ephemeral_text: core.channel.pairing.buildPairingReply({
3327
- channel: "mattermost",
3328
- idLine: `Your Mattermost user id: ${params.payload.user_id}`,
3329
- code
3330
- }) };
3331
- }
3332
- return { ephemeral_text: auth.denyReason === "unknown-channel" ? "Temporary error: unable to determine channel type. Please try again." : auth.denyReason === "dm-disabled" ? "This bot is not accepting direct messages." : auth.denyReason === "channels-disabled" ? "Model picker actions are disabled in channels." : auth.denyReason === "channel-no-allowlist" ? "Model picker actions are not configured for this channel." : "Unauthorized." };
3333
- }
3334
- const kind = auth.kind;
3335
- const chatType = auth.chatType;
3336
- const teamId = auth.channelInfo.team_id ?? params.payload.team_id ?? void 0;
3337
- const channelName = auth.channelName || void 0;
3338
- const channelDisplay = auth.channelDisplay || auth.channelName || params.payload.channel_id;
3339
- const roomLabel = auth.roomLabel;
3340
- const route = core.channel.routing.resolveAgentRoute({
3341
- cfg,
3342
- channel: "mattermost",
3343
- accountId: account.accountId,
3344
- teamId,
3345
- peer: {
3346
- kind,
3347
- id: kind === "direct" ? params.payload.user_id : params.payload.channel_id
3348
- }
3349
- });
3350
- const replyToMode = resolveMattermostReplyToMode(account, kind);
3351
- const threadContext = resolveMattermostThreadSessionContext({
3352
- baseSessionKey: route.sessionKey,
3353
- kind,
3354
- postId: params.post.id || params.payload.post_id,
3355
- replyToMode,
3356
- threadRootId: params.post.root_id
3357
- });
3358
- const modelSessionRoute = {
3359
- agentId: route.agentId,
3360
- sessionKey: threadContext.sessionKey
3361
- };
3362
- const data = await buildModelsProviderData(cfg, route.agentId);
3363
- if (data.providers.length === 0) return await updateModelPickerPost({
3364
- channelId: params.payload.channel_id,
3365
- postId: params.payload.post_id,
3366
- message: "No models available."
3367
- });
3368
- if (pickerState.action === "providers" || pickerState.action === "back") {
3369
- const currentModel = resolveMattermostModelPickerCurrentModel({
3370
- cfg,
3371
- route: modelSessionRoute,
3372
- data
3373
- });
3374
- const view = renderMattermostProviderPickerView({
3375
- ownerUserId: pickerState.ownerUserId,
3376
- data,
3377
- currentModel
3378
- });
3379
- return await updateModelPickerPost({
3380
- channelId: params.payload.channel_id,
3381
- postId: params.payload.post_id,
3382
- message: view.text,
3383
- buttons: view.buttons
3384
- });
3385
- }
3386
- if (pickerState.action === "list") {
3387
- const currentModel = resolveMattermostModelPickerCurrentModel({
3388
- cfg,
3389
- route: modelSessionRoute,
3390
- data
3391
- });
3392
- const view = renderMattermostModelsPickerView({
3393
- ownerUserId: pickerState.ownerUserId,
3394
- data,
3395
- provider: pickerState.provider,
3396
- page: pickerState.page,
3397
- currentModel
3398
- });
3399
- return await updateModelPickerPost({
3400
- channelId: params.payload.channel_id,
3401
- postId: params.payload.post_id,
3402
- message: view.text,
3403
- buttons: view.buttons
3404
- });
3405
- }
3406
- const targetModelRef = `${pickerState.provider}/${pickerState.model}`;
3407
- if (!buildMattermostAllowedModelRefs(data).has(targetModelRef)) return { ephemeral_text: `That model is no longer available: ${targetModelRef}` };
3408
- (async () => {
3409
- try {
3410
- await runModelPickerCommand({
3411
- commandText: `/model ${targetModelRef}`,
3412
- commandAuthorized: auth.commandAuthorized,
3413
- route,
3414
- sessionKey: threadContext.sessionKey,
3415
- parentSessionKey: threadContext.parentSessionKey,
3416
- channelId: params.payload.channel_id,
3417
- senderId: params.payload.user_id,
3418
- senderName: params.userName,
3419
- kind,
3420
- chatType,
3421
- channelName,
3422
- channelDisplay,
3423
- roomLabel,
3424
- teamId,
3425
- postId: params.payload.post_id,
3426
- effectiveReplyToId: threadContext.effectiveReplyToId,
3427
- deliverReplies: true
3428
- });
3429
- const updatedModel = resolveMattermostModelPickerCurrentModel({
3430
- cfg,
3431
- route: modelSessionRoute,
3432
- data,
3433
- skipCache: true
3434
- });
3435
- const view = renderMattermostModelsPickerView({
3436
- ownerUserId: pickerState.ownerUserId,
3437
- data,
3438
- provider: pickerState.provider,
3439
- page: pickerState.page,
3440
- currentModel: updatedModel
3441
- });
3442
- await updateModelPickerPost({
3443
- channelId: params.payload.channel_id,
3444
- postId: params.payload.post_id,
3445
- message: view.text,
3446
- buttons: view.buttons
3447
- });
3448
- } catch (err) {
3449
- runtime.error?.(`mattermost model picker select failed: ${String(err)}`);
3450
- }
3451
- })();
3452
- return {};
3453
- }
3454
- const handlePost = async (post, payload, messageIds) => {
3455
- const channelId = post.channel_id ?? payload.data?.channel_id ?? payload.broadcast?.channel_id;
3456
- if (!channelId) {
3457
- logVerboseMessage("mattermost: drop post (missing channel id)");
3458
- return;
3459
- }
3460
- const allMessageIds = messageIds?.length ? messageIds : post.id ? [post.id] : [];
3461
- if (allMessageIds.length === 0) {
3462
- logVerboseMessage("mattermost: drop post (missing message id)");
3463
- return;
3464
- }
3465
- const dedupeEntries = allMessageIds.map((id) => recentInboundMessages.check(`${account.accountId}:${id}`));
3466
- if (dedupeEntries.length > 0 && dedupeEntries.every(Boolean)) {
3467
- logVerboseMessage(`mattermost: drop post (dedupe account=${account.accountId} ids=${allMessageIds.length})`);
3468
- return;
3469
- }
3470
- const senderId = post.user_id ?? payload.broadcast?.user_id;
3471
- if (!senderId) {
3472
- logVerboseMessage("mattermost: drop post (missing sender id)");
3473
- return;
3474
- }
3475
- if (senderId === botUserId) {
3476
- logVerboseMessage(`mattermost: drop post (self sender=${senderId})`);
3477
- return;
3478
- }
3479
- if (isSystemPost(post)) {
3480
- logVerboseMessage(`mattermost: drop post (system post type=${post.type ?? "unknown"})`);
3481
- return;
3482
- }
3483
- const channelInfo = await resolveChannelInfo(channelId);
3484
- const kind = mapMattermostChannelTypeToChatType(payload.data?.channel_type ?? channelInfo?.type ?? void 0);
3485
- const chatType = channelChatType(kind);
3486
- const senderName = payload.data?.sender_name?.trim() || (await resolveUserInfo(senderId))?.username?.trim() || senderId;
3487
- const rawText = post.message?.trim() || "";
3488
- const dmPolicy = account.config.dmPolicy ?? "pairing";
3489
- const normalizedAllowFrom = normalizeMattermostAllowList(account.config.allowFrom ?? []);
3490
- const normalizedGroupAllowFrom = normalizeMattermostAllowList(account.config.groupAllowFrom ?? []);
3491
- const storeAllowFrom = normalizeMattermostAllowList(await readStoreAllowFromForDmPolicy({
3492
- provider: "mattermost",
3493
- accountId: account.accountId,
3494
- dmPolicy,
3495
- readStore: pairing.readStoreForDmPolicy
3496
- }));
3497
- const accessDecision = resolveDmGroupAccessWithLists({
3498
- isGroup: kind !== "direct",
3499
- dmPolicy,
3500
- groupPolicy,
3501
- allowFrom: normalizedAllowFrom,
3502
- groupAllowFrom: normalizedGroupAllowFrom,
3503
- storeAllowFrom,
3504
- isSenderAllowed: (allowFrom) => isMattermostSenderAllowed({
3505
- senderId,
3506
- senderName,
3507
- allowFrom,
3508
- allowNameMatching
3509
- })
3510
- });
3511
- const effectiveAllowFrom = accessDecision.effectiveAllowFrom;
3512
- const effectiveGroupAllowFrom = accessDecision.effectiveGroupAllowFrom;
3513
- const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
3514
- cfg,
3515
- surface: "mattermost"
3516
- });
3517
- const hasControlCommand = core.channel.text.hasControlCommand(rawText, cfg);
3518
- const isControlCommand = allowTextCommands && hasControlCommand;
3519
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
3520
- const commandDmAllowFrom = kind === "direct" ? effectiveAllowFrom : normalizedAllowFrom;
3521
- const senderAllowedForCommands = isMattermostSenderAllowed({
3522
- senderId,
3523
- senderName,
3524
- allowFrom: commandDmAllowFrom,
3525
- allowNameMatching
3526
- });
3527
- const groupAllowedForCommands = isMattermostSenderAllowed({
3528
- senderId,
3529
- senderName,
3530
- allowFrom: effectiveGroupAllowFrom,
3531
- allowNameMatching
3532
- });
3533
- const commandGate = resolveControlCommandGate({
3534
- useAccessGroups,
3535
- authorizers: [{
3536
- configured: commandDmAllowFrom.length > 0,
3537
- allowed: senderAllowedForCommands
3538
- }, {
3539
- configured: effectiveGroupAllowFrom.length > 0,
3540
- allowed: groupAllowedForCommands
3541
- }],
3542
- allowTextCommands,
3543
- hasControlCommand
3544
- });
3545
- const commandAuthorized = commandGate.commandAuthorized;
3546
- if (accessDecision.decision !== "allow") {
3547
- if (kind === "direct") {
3548
- if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED) {
3549
- logVerboseMessage(`mattermost: drop dm (dmPolicy=disabled sender=${senderId})`);
3550
- return;
3551
- }
3552
- if (accessDecision.decision === "pairing") {
3553
- const { code, created } = await pairing.upsertPairingRequest({
3554
- id: senderId,
3555
- meta: { name: senderName }
3556
- });
3557
- logVerboseMessage(`mattermost: pairing request sender=${senderId} created=${created}`);
3558
- if (created) try {
3559
- await sendMessageMattermost(`user:${senderId}`, core.channel.pairing.buildPairingReply({
3560
- channel: "mattermost",
3561
- idLine: `Your Mattermost user id: ${senderId}`,
3562
- code
3563
- }), { accountId: account.accountId });
3564
- opts.statusSink?.({ lastOutboundAt: Date.now() });
3565
- } catch (err) {
3566
- logVerboseMessage(`mattermost: pairing reply failed for ${senderId}: ${String(err)}`);
3567
- }
3568
- return;
3569
- }
3570
- logVerboseMessage(`mattermost: drop dm sender=${senderId} (dmPolicy=${dmPolicy})`);
3571
- return;
3572
- }
3573
- if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED) {
3574
- logVerboseMessage("mattermost: drop group message (groupPolicy=disabled)");
3575
- return;
3576
- }
3577
- if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST) {
3578
- logVerboseMessage("mattermost: drop group message (no group allowlist)");
3579
- return;
3580
- }
3581
- if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED) {
3582
- logVerboseMessage(`mattermost: drop group sender=${senderId} (not in groupAllowFrom)`);
3583
- return;
3584
- }
3585
- logVerboseMessage(`mattermost: drop group message (groupPolicy=${groupPolicy} reason=${accessDecision.reason})`);
3586
- return;
3587
- }
3588
- if (kind !== "direct" && commandGate.shouldBlock) {
3589
- logInboundDrop({
3590
- log: logVerboseMessage,
3591
- channel: "mattermost",
3592
- reason: "control command (unauthorized)",
3593
- target: senderId
3594
- });
3595
- return;
3596
- }
3597
- const teamId = payload.data?.team_id ?? channelInfo?.team_id ?? void 0;
3598
- const channelName = payload.data?.channel_name ?? channelInfo?.name ?? "";
3599
- const channelDisplay = payload.data?.channel_display_name ?? channelInfo?.display_name ?? channelName;
3600
- const roomLabel = channelName ? `#${channelName}` : channelDisplay || `#${channelId}`;
3601
- const route = core.channel.routing.resolveAgentRoute({
3602
- cfg,
3603
- channel: "mattermost",
3604
- accountId: account.accountId,
3605
- teamId,
3606
- peer: {
3607
- kind,
3608
- id: kind === "direct" ? senderId : channelId
3609
- }
3610
- });
3611
- const baseSessionKey = route.sessionKey;
3612
- const threadRootId = post.root_id?.trim() || void 0;
3613
- const replyToMode = resolveMattermostReplyToMode(account, kind);
3614
- const { effectiveReplyToId, sessionKey, parentSessionKey } = resolveMattermostThreadSessionContext({
3615
- baseSessionKey,
3616
- kind,
3617
- postId: post.id,
3618
- replyToMode,
3619
- threadRootId
3620
- });
3621
- const historyKey = kind === "direct" ? null : sessionKey;
3622
- const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId);
3623
- const wasMentioned = kind !== "direct" && ((botUsername ? rawText.toLowerCase().includes(`@${botUsername.toLowerCase()}`) : false) || core.channel.mentions.matchesMentionPatterns(rawText, mentionRegexes));
3624
- const pendingBody = rawText || (post.file_ids?.length ? `[Mattermost ${post.file_ids.length === 1 ? "file" : "files"}]` : "");
3625
- const pendingSender = senderName;
3626
- const recordPendingHistory = () => {
3627
- const trimmed = pendingBody.trim();
3628
- recordPendingHistoryEntryIfEnabled({
3629
- historyMap: channelHistories,
3630
- limit: historyLimit,
3631
- historyKey: historyKey ?? "",
3632
- entry: historyKey && trimmed ? {
3633
- sender: pendingSender,
3634
- body: trimmed,
3635
- timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
3636
- messageId: post.id ?? void 0
3637
- } : null
3638
- });
3639
- };
3640
- const oncharEnabled = account.chatmode === "onchar" && kind !== "direct";
3641
- const oncharPrefixes = oncharEnabled ? resolveOncharPrefixes(account.oncharPrefixes) : [];
3642
- const oncharResult = oncharEnabled ? stripOncharPrefix(rawText, oncharPrefixes) : {
3643
- triggered: false,
3644
- stripped: rawText
3645
- };
3646
- const oncharTriggered = oncharResult.triggered;
3647
- const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
3648
- const mentionDecision = evaluateMattermostMentionGate({
3649
- kind,
3650
- cfg,
3651
- accountId: account.accountId,
3652
- channelId,
3653
- threadRootId,
3654
- requireMentionOverride: account.requireMention,
3655
- resolveRequireMention: core.channel.groups.resolveRequireMention,
3656
- wasMentioned,
3657
- isControlCommand,
3658
- commandAuthorized,
3659
- oncharEnabled,
3660
- oncharTriggered,
3661
- canDetectMention
3662
- });
3663
- const { shouldRequireMention, shouldBypassMention } = mentionDecision;
3664
- if (mentionDecision.dropReason === "onchar-not-triggered") {
3665
- logVerboseMessage(`mattermost: drop group message (onchar not triggered channel=${channelId} sender=${senderId})`);
3666
- recordPendingHistory();
3667
- return;
3668
- }
3669
- if (mentionDecision.dropReason === "missing-mention") {
3670
- logVerboseMessage(`mattermost: drop group message (missing mention channel=${channelId} sender=${senderId} requireMention=${shouldRequireMention} bypass=${shouldBypassMention} canDetectMention=${canDetectMention})`);
3671
- recordPendingHistory();
3672
- return;
3673
- }
3674
- const mediaList = await resolveMattermostMedia(post.file_ids);
3675
- const mediaPlaceholder = buildMattermostAttachmentPlaceholder(mediaList);
3676
- const bodyText = normalizeMention([oncharTriggered ? oncharResult.stripped : rawText, mediaPlaceholder].filter(Boolean).join("\n").trim(), botUsername);
3677
- if (!bodyText) {
3678
- logVerboseMessage(`mattermost: drop group message (empty body after normalization channel=${channelId} sender=${senderId})`);
3679
- return;
3680
- }
3681
- core.channel.activity.record({
3682
- channel: "mattermost",
3683
- accountId: account.accountId,
3684
- direction: "inbound"
3685
- });
3686
- const fromLabel = formatInboundFromLabel({
3687
- isGroup: kind !== "direct",
3688
- groupLabel: channelDisplay || roomLabel,
3689
- groupId: channelId,
3690
- groupFallback: roomLabel || "Channel",
3691
- directLabel: senderName,
3692
- directId: senderId
3693
- });
3694
- const preview = bodyText.replace(/\s+/g, " ").slice(0, 160);
3695
- const inboundLabel = kind === "direct" ? `Mattermost DM from ${senderName}` : `Mattermost message in ${roomLabel} from ${senderName}`;
3696
- core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
3697
- sessionKey,
3698
- contextKey: `mattermost:message:${channelId}:${post.id ?? "unknown"}`
3699
- });
3700
- const textWithId = `${bodyText}\n[mattermost message id: ${post.id ?? "unknown"} channel: ${channelId}]`;
3701
- let combinedBody = core.channel.reply.formatInboundEnvelope({
3702
- channel: "Mattermost",
3703
- from: fromLabel,
3704
- timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
3705
- body: textWithId,
3706
- chatType,
3707
- sender: {
3708
- name: senderName,
3709
- id: senderId
3710
- }
3711
- });
3712
- if (historyKey) combinedBody = buildPendingHistoryContextFromMap({
3713
- historyMap: channelHistories,
3714
- historyKey,
3715
- limit: historyLimit,
3716
- currentMessage: combinedBody,
3717
- formatEntry: (entry) => core.channel.reply.formatInboundEnvelope({
3718
- channel: "Mattermost",
3719
- from: fromLabel,
3720
- timestamp: entry.timestamp,
3721
- body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} channel:${channelId}]` : ""}`,
3722
- chatType,
3723
- senderLabel: entry.sender
3724
- })
3725
- });
3726
- const to = kind === "direct" ? `user:${senderId}` : `channel:${channelId}`;
3727
- const mediaPayload = buildAgentMediaPayload(mediaList);
3728
- const commandBody = rawText.trim();
3729
- const inboundHistory = historyKey && historyLimit > 0 ? (channelHistories.get(historyKey) ?? []).map((entry) => ({
3730
- sender: entry.sender,
3731
- body: entry.body,
3732
- timestamp: entry.timestamp
3733
- })) : void 0;
3734
- const ctxPayload = core.channel.reply.finalizeInboundContext({
3735
- Body: combinedBody,
3736
- BodyForAgent: bodyText,
3737
- InboundHistory: inboundHistory,
3738
- RawBody: bodyText,
3739
- CommandBody: commandBody,
3740
- BodyForCommands: commandBody,
3741
- From: kind === "direct" ? `mattermost:${senderId}` : kind === "group" ? `mattermost:group:${channelId}` : `mattermost:channel:${channelId}`,
3742
- To: to,
3743
- SessionKey: sessionKey,
3744
- ParentSessionKey: parentSessionKey,
3745
- AccountId: route.accountId,
3746
- ChatType: chatType,
3747
- ConversationLabel: fromLabel,
3748
- GroupSubject: kind !== "direct" ? channelDisplay || roomLabel : void 0,
3749
- GroupChannel: channelName ? `#${channelName}` : void 0,
3750
- GroupSpace: teamId,
3751
- SenderName: senderName,
3752
- SenderId: senderId,
3753
- Provider: "mattermost",
3754
- Surface: "mattermost",
3755
- MessageSid: post.id ?? void 0,
3756
- MessageSids: allMessageIds.length > 1 ? allMessageIds : void 0,
3757
- MessageSidFirst: allMessageIds.length > 1 ? allMessageIds[0] : void 0,
3758
- MessageSidLast: allMessageIds.length > 1 ? allMessageIds[allMessageIds.length - 1] : void 0,
3759
- ReplyToId: effectiveReplyToId,
3760
- MessageThreadId: effectiveReplyToId,
3761
- Timestamp: typeof post.create_at === "number" ? post.create_at : void 0,
3762
- WasMentioned: kind !== "direct" ? mentionDecision.effectiveWasMentioned : void 0,
3763
- CommandAuthorized: commandAuthorized,
3764
- OriginatingChannel: "mattermost",
3765
- OriginatingTo: to,
3766
- ...mediaPayload
3767
- });
3768
- if (kind === "direct") {
3769
- const sessionCfg = cfg.session;
3770
- const storePath = core.channel.session.resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
3771
- await core.channel.session.updateLastRoute({
3772
- storePath,
3773
- sessionKey: route.mainSessionKey,
3774
- deliveryContext: {
3775
- channel: "mattermost",
3776
- to,
3777
- accountId: route.accountId
3778
- }
3779
- });
3780
- }
3781
- const previewLine = bodyText.slice(0, 200).replace(/\n/g, "\\n");
3782
- logVerboseMessage(`mattermost inbound: from=${ctxPayload.From} len=${bodyText.length} preview="${previewLine}"`);
3783
- const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "mattermost", account.accountId, { fallbackLimit: account.textChunkLimit ?? 4e3 });
3784
- const tableMode = core.channel.text.resolveMarkdownTableMode({
3785
- cfg,
3786
- channel: "mattermost",
3787
- accountId: account.accountId
3788
- });
3789
- const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
3790
- cfg,
3791
- agentId: route.agentId,
3792
- channel: "mattermost",
3793
- accountId: account.accountId
3794
- });
3795
- const typingCallbacks = createTypingCallbacks({
3796
- start: () => sendTypingIndicator(channelId, effectiveReplyToId),
3797
- onStartError: (err) => {
3798
- logTypingFailure({
3799
- log: (message) => logger.debug?.(message),
3800
- channel: "mattermost",
3801
- target: channelId,
3802
- error: err
3803
- });
3804
- }
3805
- });
3806
- const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
3807
- ...prefixOptions,
3808
- humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
3809
- typingCallbacks,
3810
- deliver: async (payload) => {
3811
- await deliverMattermostReplyPayload({
3812
- core,
3813
- cfg,
3814
- payload,
3815
- to,
3816
- accountId: account.accountId,
3817
- agentId: route.agentId,
3818
- replyToId: resolveMattermostReplyRootId({
3819
- threadRootId: effectiveReplyToId,
3820
- replyToId: payload.replyToId
3821
- }),
3822
- textLimit,
3823
- tableMode,
3824
- sendMessage: sendMessageMattermost
3825
- });
3826
- runtime.log?.(`delivered reply to ${to}`);
3827
- },
3828
- onError: (err, info) => {
3829
- runtime.error?.(`mattermost ${info.kind} reply failed: ${String(err)}`);
3830
- }
3831
- });
3832
- await core.channel.reply.withReplyDispatcher({
3833
- dispatcher,
3834
- onSettled: () => {
3835
- markDispatchIdle();
3836
- },
3837
- run: () => core.channel.reply.dispatchReplyFromConfig({
3838
- ctx: ctxPayload,
3839
- cfg,
3840
- dispatcher,
3841
- replyOptions: {
3842
- ...replyOptions,
3843
- disableBlockStreaming: typeof account.blockStreaming === "boolean" ? !account.blockStreaming : void 0,
3844
- onModelSelected
3845
- }
3846
- })
3847
- });
3848
- if (historyKey) clearHistoryEntriesIfEnabled({
3849
- historyMap: channelHistories,
3850
- historyKey,
3851
- limit: historyLimit
3852
- });
3853
- };
3854
- const handleReactionEvent = async (payload) => {
3855
- const reactionData = payload.data?.reaction;
3856
- if (!reactionData) return;
3857
- let reaction = null;
3858
- if (typeof reactionData === "string") try {
3859
- reaction = JSON.parse(reactionData);
3860
- } catch {
3861
- return;
3862
- }
3863
- else if (typeof reactionData === "object") reaction = reactionData;
3864
- if (!reaction) return;
3865
- const userId = reaction.user_id?.trim();
3866
- const postId = reaction.post_id?.trim();
3867
- const emojiName = reaction.emoji_name?.trim();
3868
- if (!userId || !postId || !emojiName) return;
3869
- if (userId === botUserId) return;
3870
- const action = payload.event === "reaction_removed" ? "removed" : "added";
3871
- const senderName = (await resolveUserInfo(userId))?.username?.trim() || userId;
3872
- const channelId = payload.broadcast?.channel_id;
3873
- if (!channelId) {
3874
- logVerboseMessage(`mattermost: drop reaction (no channel_id in broadcast, cannot enforce policy)`);
3875
- return;
3876
- }
3877
- const channelInfo = await resolveChannelInfo(channelId);
3878
- if (!channelInfo?.type) {
3879
- logVerboseMessage(`mattermost: drop reaction (cannot resolve channel type for ${channelId})`);
3880
- return;
3881
- }
3882
- const kind = mapMattermostChannelTypeToChatType(channelInfo.type);
3883
- const dmPolicy = account.config.dmPolicy ?? "pairing";
3884
- const storeAllowFrom = normalizeMattermostAllowList(await readStoreAllowFromForDmPolicy({
3885
- provider: "mattermost",
3886
- accountId: account.accountId,
3887
- dmPolicy,
3888
- readStore: pairing.readStoreForDmPolicy
3889
- }));
3890
- const reactionAccess = resolveDmGroupAccessWithLists({
3891
- isGroup: kind !== "direct",
3892
- dmPolicy,
3893
- groupPolicy,
3894
- allowFrom: normalizeMattermostAllowList(account.config.allowFrom ?? []),
3895
- groupAllowFrom: normalizeMattermostAllowList(account.config.groupAllowFrom ?? []),
3896
- storeAllowFrom,
3897
- isSenderAllowed: (allowFrom) => isMattermostSenderAllowed({
3898
- senderId: userId,
3899
- senderName,
3900
- allowFrom,
3901
- allowNameMatching
3902
- })
3903
- });
3904
- if (reactionAccess.decision !== "allow") {
3905
- if (kind === "direct") logVerboseMessage(`mattermost: drop reaction (dmPolicy=${dmPolicy} sender=${userId} reason=${reactionAccess.reason})`);
3906
- else logVerboseMessage(`mattermost: drop reaction (groupPolicy=${groupPolicy} sender=${userId} reason=${reactionAccess.reason} channel=${channelId})`);
3907
- return;
3908
- }
3909
- const teamId = channelInfo?.team_id ?? void 0;
3910
- const sessionKey = core.channel.routing.resolveAgentRoute({
3911
- cfg,
3912
- channel: "mattermost",
3913
- accountId: account.accountId,
3914
- teamId,
3915
- peer: {
3916
- kind,
3917
- id: kind === "direct" ? userId : channelId
3918
- }
3919
- }).sessionKey;
3920
- const eventText = `Mattermost reaction ${action}: :${emojiName}: by @${senderName} on post ${postId} in channel ${channelId}`;
3921
- core.system.enqueueSystemEvent(eventText, {
3922
- sessionKey,
3923
- contextKey: `mattermost:reaction:${postId}:${emojiName}:${userId}:${action}`
3924
- });
3925
- logVerboseMessage(`mattermost reaction: ${action} :${emojiName}: by ${senderName} on ${postId}`);
3926
- };
3927
- const inboundDebounceMs = core.channel.debounce.resolveInboundDebounceMs({
3928
- cfg,
3929
- channel: "mattermost"
3930
- });
3931
- const debouncer = core.channel.debounce.createInboundDebouncer({
3932
- debounceMs: inboundDebounceMs,
3933
- buildKey: (entry) => {
3934
- const channelId = entry.post.channel_id ?? entry.payload.data?.channel_id ?? entry.payload.broadcast?.channel_id;
3935
- if (!channelId) return null;
3936
- const threadId = entry.post.root_id?.trim();
3937
- const threadKey = threadId ? `thread:${threadId}` : "channel";
3938
- return `mattermost:${account.accountId}:${channelId}:${threadKey}`;
3939
- },
3940
- shouldDebounce: (entry) => {
3941
- if (entry.post.file_ids && entry.post.file_ids.length > 0) return false;
3942
- const text = entry.post.message?.trim() ?? "";
3943
- if (!text) return false;
3944
- return !core.channel.text.hasControlCommand(text, cfg);
3945
- },
3946
- onFlush: async (entries) => {
3947
- const last = entries.at(-1);
3948
- if (!last) return;
3949
- if (entries.length === 1) {
3950
- await handlePost(last.post, last.payload);
3951
- return;
3952
- }
3953
- const combinedText = entries.map((entry) => entry.post.message?.trim() ?? "").filter(Boolean).join("\n");
3954
- const mergedPost = {
3955
- ...last.post,
3956
- message: combinedText,
3957
- file_ids: []
3958
- };
3959
- const ids = entries.map((entry) => entry.post.id).filter(Boolean);
3960
- await handlePost(mergedPost, last.payload, ids.length > 0 ? ids : void 0);
3961
- },
3962
- onError: (err) => {
3963
- runtime.error?.(`mattermost debounce flush failed: ${String(err)}`);
3964
- }
3965
- });
3966
- const wsUrl = buildMattermostWsUrl(baseUrl);
3967
- let seq = 1;
3968
- const connectOnce = createMattermostConnectOnce({
3969
- wsUrl,
3970
- botToken,
3971
- abortSignal: opts.abortSignal,
3972
- statusSink: opts.statusSink,
3973
- runtime,
3974
- webSocketFactory: opts.webSocketFactory,
3975
- nextSeq: () => seq++,
3976
- onPosted: async (post, payload) => {
3977
- await debouncer.enqueue({
3978
- post,
3979
- payload
3980
- });
3981
- },
3982
- onReaction: async (payload) => {
3983
- await handleReactionEvent(payload);
3984
- }
3985
- });
3986
- let slashShutdownCleanup = null;
3987
- if (slashEnabled) {
3988
- const runAbortCleanup = () => {
3989
- if (slashShutdownCleanup) return;
3990
- const commands = getSlashCommandState(account.accountId)?.registeredCommands ?? [];
3991
- deactivateSlashCommands(account.accountId);
3992
- slashShutdownCleanup = cleanupSlashCommands({
3993
- client,
3994
- commands,
3995
- log: (msg) => runtime.log?.(msg)
3996
- }).catch((err) => {
3997
- runtime.error?.(`mattermost: slash cleanup failed: ${String(err)}`);
3998
- });
3999
- };
4000
- if (opts.abortSignal?.aborted) runAbortCleanup();
4001
- else opts.abortSignal?.addEventListener("abort", runAbortCleanup, { once: true });
4002
- }
4003
- try {
4004
- await runWithReconnect(connectOnce, {
4005
- abortSignal: opts.abortSignal,
4006
- jitterRatio: .2,
4007
- onError: (err) => {
4008
- runtime.error?.(`mattermost connection failed: ${String(err)}`);
4009
- opts.statusSink?.({
4010
- lastError: String(err),
4011
- connected: false
4012
- });
4013
- },
4014
- onReconnect: (delayMs) => {
4015
- runtime.log?.(`mattermost reconnecting in ${Math.round(delayMs / 1e3)}s`);
4016
- }
4017
- });
4018
- } finally {
4019
- unregisterInteractions?.();
4020
- }
4021
- if (slashShutdownCleanup) await slashShutdownCleanup;
4022
- }
4023
- //#endregion
4024
- //#region extensions/mattermost/src/mattermost/probe.ts
4025
- async function probeMattermost(baseUrl, botToken, timeoutMs = 2500) {
4026
- const normalized = normalizeMattermostBaseUrl(baseUrl);
4027
- if (!normalized) return {
4028
- ok: false,
4029
- error: "baseUrl missing"
4030
- };
4031
- const url = `${normalized}/api/v4/users/me`;
4032
- const start = Date.now();
4033
- const controller = timeoutMs > 0 ? new AbortController() : void 0;
4034
- let timer = null;
4035
- if (controller) timer = setTimeout(() => controller.abort(), timeoutMs);
4036
- try {
4037
- const res = await fetch(url, {
4038
- headers: { Authorization: `Bearer ${botToken}` },
4039
- signal: controller?.signal
4040
- });
4041
- const elapsedMs = Date.now() - start;
4042
- if (!res.ok) {
4043
- const detail = await readMattermostError(res);
4044
- return {
4045
- ok: false,
4046
- status: res.status,
4047
- error: detail || res.statusText,
4048
- elapsedMs
4049
- };
4050
- }
4051
- const bot = await res.json();
4052
- return {
4053
- ok: true,
4054
- status: res.status,
4055
- elapsedMs,
4056
- bot
4057
- };
4058
- } catch (err) {
4059
- return {
4060
- ok: false,
4061
- status: null,
4062
- error: err instanceof Error ? err.message : String(err),
4063
- elapsedMs: Date.now() - start
4064
- };
4065
- } finally {
4066
- if (timer) clearTimeout(timer);
4067
- }
4068
- }
4069
- //#endregion
4070
- //#region extensions/mattermost/src/mattermost/reactions.ts
4071
- const BOT_USER_CACHE_TTL_MS = 10 * 6e4;
4072
- const botUserIdCache = /* @__PURE__ */ new Map();
4073
- async function resolveBotUserId(client, cacheKey) {
4074
- const cached = botUserIdCache.get(cacheKey);
4075
- if (cached && cached.expiresAt > Date.now()) return cached.userId;
4076
- const userId = (await fetchMattermostMe(client))?.id?.trim();
4077
- if (!userId) return null;
4078
- botUserIdCache.set(cacheKey, {
4079
- userId,
4080
- expiresAt: Date.now() + BOT_USER_CACHE_TTL_MS
4081
- });
4082
- return userId;
4083
- }
4084
- async function addMattermostReaction(params) {
4085
- return runMattermostReaction(params, {
4086
- action: "add",
4087
- mutation: createReaction
4088
- });
4089
- }
4090
- async function removeMattermostReaction(params) {
4091
- return runMattermostReaction(params, {
4092
- action: "remove",
4093
- mutation: deleteReaction
4094
- });
4095
- }
4096
- async function runMattermostReaction(params, options) {
4097
- const resolved = resolveMattermostAccount({
4098
- cfg: params.cfg,
4099
- accountId: params.accountId
4100
- });
4101
- const baseUrl = resolved.baseUrl?.trim();
4102
- const botToken = resolved.botToken?.trim();
4103
- if (!baseUrl || !botToken) return {
4104
- ok: false,
4105
- error: "Mattermost botToken/baseUrl missing."
4106
- };
4107
- const client = createMattermostClient({
4108
- baseUrl,
4109
- botToken,
4110
- fetchImpl: params.fetchImpl
4111
- });
4112
- const userId = await resolveBotUserId(client, `${baseUrl}:${botToken}`);
4113
- if (!userId) return {
4114
- ok: false,
4115
- error: "Mattermost reactions failed: could not resolve bot user id."
4116
- };
4117
- try {
4118
- await options.mutation(client, {
4119
- userId,
4120
- postId: params.postId,
4121
- emojiName: params.emojiName
4122
- });
4123
- } catch (err) {
4124
- return {
4125
- ok: false,
4126
- error: `Mattermost ${options.action} reaction failed: ${String(err)}`
4127
- };
4128
- }
4129
- return { ok: true };
4130
- }
4131
- async function createReaction(client, params) {
4132
- await client.request("/reactions", {
4133
- method: "POST",
4134
- body: JSON.stringify({
4135
- user_id: params.userId,
4136
- post_id: params.postId,
4137
- emoji_name: params.emojiName
4138
- })
4139
- });
4140
- }
4141
- async function deleteReaction(client, params) {
4142
- const emoji = encodeURIComponent(params.emojiName);
4143
- await client.request(`/users/${params.userId}/posts/${params.postId}/reactions/${emoji}`, { method: "DELETE" });
4144
- }
4145
- //#endregion
4146
- //#region extensions/mattermost/src/normalize.ts
4147
- function normalizeMattermostMessagingTarget(raw) {
4148
- const trimmed = raw.trim();
4149
- if (!trimmed) return;
4150
- const lower = trimmed.toLowerCase();
4151
- if (lower.startsWith("channel:")) {
4152
- const id = trimmed.slice(8).trim();
4153
- return id ? `channel:${id}` : void 0;
4154
- }
4155
- if (lower.startsWith("group:")) {
4156
- const id = trimmed.slice(6).trim();
4157
- return id ? `channel:${id}` : void 0;
4158
- }
4159
- if (lower.startsWith("user:")) {
4160
- const id = trimmed.slice(5).trim();
4161
- return id ? `user:${id}` : void 0;
4162
- }
4163
- if (lower.startsWith("mattermost:")) {
4164
- const id = trimmed.slice(11).trim();
4165
- return id ? `user:${id}` : void 0;
4166
- }
4167
- if (trimmed.startsWith("@")) {
4168
- const id = trimmed.slice(1).trim();
4169
- return id ? `@${id}` : void 0;
4170
- }
4171
- if (trimmed.startsWith("#")) return;
4172
- }
4173
- function looksLikeMattermostTargetId(raw, normalized) {
4174
- const trimmed = raw.trim();
4175
- if (!trimmed) return false;
4176
- if (/^(user|channel|group|mattermost):/i.test(trimmed)) return true;
4177
- if (trimmed.startsWith("@")) return true;
4178
- return /^[a-z0-9]{26}$/i.test(trimmed) || /^[a-z0-9]{26}__[a-z0-9]{26}$/i.test(trimmed);
4179
- }
4180
- //#endregion
4181
- //#region extensions/mattermost/src/setup-core.ts
4182
- const channel$1 = "mattermost";
4183
- function isMattermostConfigured(account) {
4184
- return (Boolean(account.botToken?.trim()) || hasConfiguredSecretInput(account.config.botToken)) && Boolean(account.baseUrl);
4185
- }
4186
- function resolveMattermostAccountWithSecrets(cfg, accountId) {
4187
- return resolveMattermostAccount({
4188
- cfg,
4189
- accountId,
4190
- allowUnresolvedSecretRef: true
4191
- });
4192
- }
4193
- const mattermostSetupAdapter = {
4194
- resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
4195
- applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
4196
- cfg,
4197
- channelKey: channel$1,
4198
- accountId,
4199
- name
4200
- }),
4201
- validateInput: ({ accountId, input }) => {
4202
- const token = input.botToken ?? input.token;
4203
- const baseUrl = normalizeMattermostBaseUrl(input.httpUrl);
4204
- if (input.useEnv && accountId !== "default") return "Mattermost env vars can only be used for the default account.";
4205
- if (!input.useEnv && (!token || !baseUrl)) return "Mattermost requires --bot-token and --http-url (or --use-env).";
4206
- if (input.httpUrl && !baseUrl) return "Mattermost --http-url must include a valid base URL.";
4207
- return null;
4208
- },
4209
- applyAccountConfig: ({ cfg, accountId, input }) => {
4210
- const token = input.botToken ?? input.token;
4211
- const baseUrl = normalizeMattermostBaseUrl(input.httpUrl);
4212
- const namedConfig = applyAccountNameToChannelSection({
4213
- cfg,
4214
- channelKey: channel$1,
4215
- accountId,
4216
- name: input.name
4217
- });
4218
- return applySetupAccountConfigPatch({
4219
- cfg: accountId !== "default" ? migrateBaseNameToDefaultAccount({
4220
- cfg: namedConfig,
4221
- channelKey: channel$1
4222
- }) : namedConfig,
4223
- channelKey: channel$1,
4224
- accountId,
4225
- patch: input.useEnv ? {} : {
4226
- ...token ? { botToken: token } : {},
4227
- ...baseUrl ? { baseUrl } : {}
4228
- }
4229
- });
4230
- }
4231
- };
4232
- //#endregion
4233
- //#region extensions/mattermost/src/setup-surface.ts
4234
- const channel = "mattermost";
4235
- const mattermostSetupWizard = {
4236
- channel,
4237
- status: {
4238
- configuredLabel: "configured",
4239
- unconfiguredLabel: "needs token + url",
4240
- configuredHint: "configured",
4241
- unconfiguredHint: "needs setup",
4242
- configuredScore: 2,
4243
- unconfiguredScore: 1,
4244
- resolveConfigured: ({ cfg }) => listMattermostAccountIds(cfg).some((accountId) => isMattermostConfigured(resolveMattermostAccountWithSecrets(cfg, accountId)))
4245
- },
4246
- introNote: {
4247
- title: "Mattermost bot token",
4248
- lines: [
4249
- "1) Mattermost System Console -> Integrations -> Bot Accounts",
4250
- "2) Create a bot + copy its token",
4251
- "3) Use your server base URL (e.g., https://chat.example.com)",
4252
- "Tip: the bot must be a member of any channel you want it to monitor.",
4253
- `Docs: ${formatDocsLink("/mattermost", "mattermost")}`
4254
- ],
4255
- shouldShow: ({ cfg, accountId }) => !isMattermostConfigured(resolveMattermostAccountWithSecrets(cfg, accountId))
4256
- },
4257
- envShortcut: {
4258
- prompt: "MATTERMOST_BOT_TOKEN + MATTERMOST_URL detected. Use env vars?",
4259
- preferredEnvVar: "MATTERMOST_BOT_TOKEN",
4260
- isAvailable: ({ cfg, accountId }) => {
4261
- if (accountId !== "default") return false;
4262
- const resolvedAccount = resolveMattermostAccountWithSecrets(cfg, accountId);
4263
- const hasConfigValues = hasConfiguredSecretInput(resolvedAccount.config.botToken) || Boolean(resolvedAccount.config.baseUrl?.trim());
4264
- return Boolean(process.env.MATTERMOST_BOT_TOKEN?.trim() && process.env.MATTERMOST_URL?.trim() && !hasConfigValues);
4265
- },
4266
- apply: ({ cfg, accountId }) => applySetupAccountConfigPatch({
4267
- cfg,
4268
- channelKey: channel,
4269
- accountId,
4270
- patch: {}
4271
- })
4272
- },
4273
- credentials: [{
4274
- inputKey: "botToken",
4275
- providerHint: channel,
4276
- credentialLabel: "bot token",
4277
- preferredEnvVar: "MATTERMOST_BOT_TOKEN",
4278
- envPrompt: "MATTERMOST_BOT_TOKEN + MATTERMOST_URL detected. Use env vars?",
4279
- keepPrompt: "Mattermost bot token already configured. Keep it?",
4280
- inputPrompt: "Enter Mattermost bot token",
4281
- inspect: ({ cfg, accountId }) => {
4282
- const resolvedAccount = resolveMattermostAccountWithSecrets(cfg, accountId);
4283
- return {
4284
- accountConfigured: isMattermostConfigured(resolvedAccount),
4285
- hasConfiguredValue: hasConfiguredSecretInput(resolvedAccount.config.botToken)
4286
- };
4287
- }
4288
- }],
4289
- textInputs: [{
4290
- inputKey: "httpUrl",
4291
- message: "Enter Mattermost base URL",
4292
- confirmCurrentValue: false,
4293
- currentValue: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId).baseUrl ?? process.env.MATTERMOST_URL?.trim(),
4294
- initialValue: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId).baseUrl ?? process.env.MATTERMOST_URL?.trim(),
4295
- shouldPrompt: ({ cfg, accountId, credentialValues, currentValue }) => {
4296
- const resolvedAccount = resolveMattermostAccountWithSecrets(cfg, accountId);
4297
- const tokenConfigured = Boolean(resolvedAccount.botToken?.trim()) || hasConfiguredSecretInput(resolvedAccount.config.botToken);
4298
- return Boolean(credentialValues.botToken) || !tokenConfigured || !currentValue;
4299
- },
4300
- validate: ({ value }) => normalizeMattermostBaseUrl(value) ? void 0 : "Mattermost base URL must include a valid base URL.",
4301
- normalizeValue: ({ value }) => normalizeMattermostBaseUrl(value) ?? value.trim()
4302
- }],
4303
- disable: (cfg) => ({
4304
- ...cfg,
4305
- channels: {
4306
- ...cfg.channels,
4307
- mattermost: {
4308
- ...cfg.channels?.mattermost,
4309
- enabled: false
4310
- }
4311
- }
4312
- })
4313
- };
4314
- //#endregion
4315
- //#region extensions/mattermost/src/channel.ts
4316
- const mattermostMessageActions = {
4317
- listActions: ({ cfg }) => {
4318
- const enabledAccounts = listMattermostAccountIds(cfg).map((accountId) => resolveMattermostAccount({
4319
- cfg,
4320
- accountId
4321
- })).filter((account) => account.enabled).filter((account) => Boolean(account.botToken?.trim() && account.baseUrl?.trim()));
4322
- const actions = [];
4323
- if (enabledAccounts.length > 0) actions.push("send");
4324
- const baseReactions = (cfg.channels?.mattermost?.actions)?.reactions;
4325
- if (enabledAccounts.some((account) => {
4326
- return (account.config.actions?.reactions ?? baseReactions ?? true) !== false;
4327
- })) actions.push("react");
4328
- return actions;
4329
- },
4330
- supportsAction: ({ action }) => {
4331
- return action === "send" || action === "react";
4332
- },
4333
- getCapabilities: ({ cfg }) => {
4334
- return listMattermostAccountIds(cfg).map((id) => resolveMattermostAccount({
4335
- cfg,
4336
- accountId: id
4337
- })).filter((a) => a.enabled && a.botToken?.trim() && a.baseUrl?.trim()).length > 0 ? ["buttons"] : [];
4338
- },
4339
- handleAction: async ({ action, params, cfg, accountId }) => {
4340
- if (action === "react") {
4341
- const mmBase = cfg?.channels?.mattermost;
4342
- const accounts = mmBase?.accounts;
4343
- const resolvedAccountId = accountId ?? resolveDefaultMattermostAccountId(cfg);
4344
- const acctActions = (accounts?.[resolvedAccountId])?.actions;
4345
- const baseActions = mmBase?.actions;
4346
- if (!(acctActions?.reactions ?? baseActions?.reactions ?? true)) throw new Error("Mattermost reactions are disabled in config");
4347
- const postId = (typeof params?.messageId === "string" ? params.messageId : typeof params?.postId === "string" ? params.postId : "").trim();
4348
- if (!postId) throw new Error("Mattermost react requires messageId (post id)");
4349
- const emojiName = (typeof params?.emoji === "string" ? params.emoji : "").trim().replace(/^:+|:+$/g, "");
4350
- if (!emojiName) throw new Error("Mattermost react requires emoji");
4351
- if (params?.remove === true) {
4352
- const result = await removeMattermostReaction({
4353
- cfg,
4354
- postId,
4355
- emojiName,
4356
- accountId: resolvedAccountId
4357
- });
4358
- if (!result.ok) throw new Error(result.error);
4359
- return {
4360
- content: [{
4361
- type: "text",
4362
- text: `Removed reaction :${emojiName}: from ${postId}`
4363
- }],
4364
- details: {}
4365
- };
4366
- }
4367
- const result = await addMattermostReaction({
4368
- cfg,
4369
- postId,
4370
- emojiName,
4371
- accountId: resolvedAccountId
4372
- });
4373
- if (!result.ok) throw new Error(result.error);
4374
- return {
4375
- content: [{
4376
- type: "text",
4377
- text: `Reacted with :${emojiName}: on ${postId}`
4378
- }],
4379
- details: {}
4380
- };
4381
- }
4382
- if (action !== "send") throw new Error(`Unsupported Mattermost action: ${action}`);
4383
- const to = typeof params.to === "string" ? params.to.trim() : typeof params.target === "string" ? params.target.trim() : "";
4384
- if (!to) throw new Error("Mattermost send requires a target (to).");
4385
- const message = typeof params.message === "string" ? params.message : "";
4386
- const replyToId = readMattermostReplyToId(params);
4387
- const resolvedAccountId = accountId || void 0;
4388
- const mediaUrl = typeof params.media === "string" ? params.media.trim() || void 0 : void 0;
4389
- const result = await sendMessageMattermost(to, message, {
4390
- accountId: resolvedAccountId,
4391
- replyToId,
4392
- buttons: Array.isArray(params.buttons) ? params.buttons : void 0,
4393
- attachmentText: typeof params.attachmentText === "string" ? params.attachmentText : void 0,
4394
- mediaUrl
4395
- });
4396
- return {
4397
- content: [{
4398
- type: "text",
4399
- text: JSON.stringify({
4400
- ok: true,
4401
- channel: "mattermost",
4402
- messageId: result.messageId,
4403
- channelId: result.channelId
4404
- })
4405
- }],
4406
- details: {}
4407
- };
4408
- }
4409
- };
4410
- const meta = {
4411
- id: "mattermost",
4412
- label: "Mattermost",
4413
- selectionLabel: "Mattermost (plugin)",
4414
- detailLabel: "Mattermost Bot",
4415
- docsPath: "/channels/mattermost",
4416
- docsLabel: "mattermost",
4417
- blurb: "self-hosted Slack-style chat; install the plugin to enable.",
4418
- systemImage: "bubble.left.and.bubble.right",
4419
- order: 65,
4420
- quickstartAllowFrom: true
4421
- };
4422
- function readMattermostReplyToId(params) {
4423
- const readNormalizedValue = (value) => {
4424
- if (typeof value !== "string") return;
4425
- return value.trim() || void 0;
4426
- };
4427
- return readNormalizedValue(params.replyToId) ?? readNormalizedValue(params.replyTo);
4428
- }
4429
- function normalizeAllowEntry(entry) {
4430
- return entry.trim().replace(/^(mattermost|user):/i, "").replace(/^@/, "").toLowerCase();
4431
- }
4432
- function formatAllowEntry(entry) {
4433
- const trimmed = entry.trim();
4434
- if (!trimmed) return "";
4435
- if (trimmed.startsWith("@")) {
4436
- const username = trimmed.slice(1).trim();
4437
- return username ? `@${username.toLowerCase()}` : "";
4438
- }
4439
- return trimmed.replace(/^(mattermost|user):/i, "").toLowerCase();
4440
- }
4441
- const mattermostConfigAccessors = createScopedAccountConfigAccessors({
4442
- resolveAccount: ({ cfg, accountId }) => resolveMattermostAccount({
4443
- cfg,
4444
- accountId
4445
- }),
4446
- resolveAllowFrom: (account) => account.config.allowFrom,
4447
- formatAllowFrom: (allowFrom) => formatNormalizedAllowFromEntries({
4448
- allowFrom,
4449
- normalizeEntry: formatAllowEntry
4450
- })
4451
- });
4452
- const mattermostPlugin = {
4453
- id: "mattermost",
4454
- meta: { ...meta },
4455
- setup: mattermostSetupAdapter,
4456
- setupWizard: mattermostSetupWizard,
4457
- pairing: {
4458
- idLabel: "mattermostUserId",
4459
- normalizeAllowEntry: (entry) => normalizeAllowEntry(entry),
4460
- notifyApproval: async ({ id }) => {
4461
- console.log(`[mattermost] User ${id} approved for pairing`);
4462
- }
4463
- },
4464
- capabilities: {
4465
- chatTypes: [
4466
- "direct",
4467
- "channel",
4468
- "group",
4469
- "thread"
4470
- ],
4471
- reactions: true,
4472
- threads: true,
4473
- media: true,
4474
- nativeCommands: true
4475
- },
4476
- streaming: { blockStreamingCoalesceDefaults: {
4477
- minChars: 1500,
4478
- idleMs: 1e3
4479
- } },
4480
- threading: { resolveReplyToMode: ({ cfg, accountId, chatType }) => {
4481
- return resolveMattermostReplyToMode(resolveMattermostAccount({
4482
- cfg,
4483
- accountId: accountId ?? "default"
4484
- }), chatType === "direct" || chatType === "group" || chatType === "channel" ? chatType : "channel");
4485
- } },
4486
- reload: { configPrefixes: ["channels.mattermost"] },
4487
- configSchema: buildChannelConfigSchema(MattermostConfigSchema),
4488
- config: {
4489
- listAccountIds: (cfg) => listMattermostAccountIds(cfg),
4490
- resolveAccount: (cfg, accountId) => resolveMattermostAccount({
4491
- cfg,
4492
- accountId
4493
- }),
4494
- defaultAccountId: (cfg) => resolveDefaultMattermostAccountId(cfg),
4495
- setAccountEnabled: ({ cfg, accountId, enabled }) => setAccountEnabledInConfigSection({
4496
- cfg,
4497
- sectionKey: "mattermost",
4498
- accountId,
4499
- enabled,
4500
- allowTopLevel: true
4501
- }),
4502
- deleteAccount: ({ cfg, accountId }) => deleteAccountFromConfigSection({
4503
- cfg,
4504
- sectionKey: "mattermost",
4505
- accountId,
4506
- clearBaseFields: [
4507
- "botToken",
4508
- "baseUrl",
4509
- "name"
4510
- ]
4511
- }),
4512
- isConfigured: (account) => Boolean(account.botToken && account.baseUrl),
4513
- describeAccount: (account) => ({
4514
- accountId: account.accountId,
4515
- name: account.name,
4516
- enabled: account.enabled,
4517
- configured: Boolean(account.botToken && account.baseUrl),
4518
- botTokenSource: account.botTokenSource,
4519
- baseUrl: account.baseUrl
4520
- }),
4521
- ...mattermostConfigAccessors
4522
- },
4523
- security: {
4524
- resolveDmPolicy: ({ cfg, accountId, account }) => {
4525
- return buildAccountScopedDmSecurityPolicy({
4526
- cfg,
4527
- channelKey: "mattermost",
4528
- accountId,
4529
- fallbackAccountId: account.accountId ?? "default",
4530
- policy: account.config.dmPolicy,
4531
- allowFrom: account.config.allowFrom ?? [],
4532
- policyPathSuffix: "dmPolicy",
4533
- normalizeEntry: (raw) => normalizeAllowEntry(raw)
4534
- });
4535
- },
4536
- collectWarnings: ({ account, cfg }) => {
4537
- return collectAllowlistProviderRestrictSendersWarnings({
4538
- cfg,
4539
- providerConfigPresent: cfg.channels?.mattermost !== void 0,
4540
- configuredGroupPolicy: account.config.groupPolicy,
4541
- surface: "Mattermost channels",
4542
- openScope: "any member",
4543
- groupPolicyPath: "channels.mattermost.groupPolicy",
4544
- groupAllowFromPath: "channels.mattermost.groupAllowFrom"
4545
- });
4546
- }
4547
- },
4548
- groups: { resolveRequireMention: resolveMattermostGroupRequireMention },
4549
- actions: mattermostMessageActions,
4550
- directory: {
4551
- listGroups: async (params) => listMattermostDirectoryGroups(params),
4552
- listGroupsLive: async (params) => listMattermostDirectoryGroups(params),
4553
- listPeers: async (params) => listMattermostDirectoryPeers(params),
4554
- listPeersLive: async (params) => listMattermostDirectoryPeers(params)
4555
- },
4556
- messaging: {
4557
- normalizeTarget: normalizeMattermostMessagingTarget,
4558
- targetResolver: {
4559
- looksLikeId: looksLikeMattermostTargetId,
4560
- hint: "<channelId|user:ID|channel:ID>",
4561
- resolveTarget: async ({ cfg, accountId, input }) => {
4562
- const resolved = await resolveMattermostOpaqueTarget({
4563
- input,
4564
- cfg,
4565
- accountId
4566
- });
4567
- if (!resolved) return null;
4568
- return {
4569
- to: resolved.to,
4570
- kind: resolved.kind,
4571
- source: "directory"
4572
- };
4573
- }
4574
- }
4575
- },
4576
- outbound: {
4577
- deliveryMode: "direct",
4578
- chunker: (text, limit) => getMattermostRuntime().channel.text.chunkMarkdownText(text, limit),
4579
- chunkerMode: "markdown",
4580
- textChunkLimit: 4e3,
4581
- resolveTarget: ({ to }) => {
4582
- const trimmed = to?.trim();
4583
- if (!trimmed) return {
4584
- ok: false,
4585
- error: /* @__PURE__ */ new Error("Delivering to Mattermost requires --to <channelId|@username|user:ID|channel:ID>")
4586
- };
4587
- return {
4588
- ok: true,
4589
- to: trimmed
4590
- };
4591
- },
4592
- sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
4593
- return {
4594
- channel: "mattermost",
4595
- ...await sendMessageMattermost(to, text, {
4596
- cfg,
4597
- accountId: accountId ?? void 0,
4598
- replyToId: replyToId ?? (threadId != null ? String(threadId) : void 0)
4599
- })
4600
- };
4601
- },
4602
- sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, accountId, replyToId, threadId }) => {
4603
- return {
4604
- channel: "mattermost",
4605
- ...await sendMessageMattermost(to, text, {
4606
- cfg,
4607
- accountId: accountId ?? void 0,
4608
- mediaUrl,
4609
- mediaLocalRoots,
4610
- replyToId: replyToId ?? (threadId != null ? String(threadId) : void 0)
4611
- })
4612
- };
4613
- }
4614
- },
4615
- status: {
4616
- defaultRuntime: {
4617
- accountId: DEFAULT_ACCOUNT_ID,
4618
- running: false,
4619
- connected: false,
4620
- lastConnectedAt: null,
4621
- lastDisconnect: null,
4622
- lastStartAt: null,
4623
- lastStopAt: null,
4624
- lastError: null
4625
- },
4626
- buildChannelSummary: ({ snapshot }) => buildPassiveProbedChannelStatusSummary(snapshot, {
4627
- botTokenSource: snapshot.botTokenSource ?? "none",
4628
- connected: snapshot.connected ?? false,
4629
- baseUrl: snapshot.baseUrl ?? null
4630
- }),
4631
- probeAccount: async ({ account, timeoutMs }) => {
4632
- const token = account.botToken?.trim();
4633
- const baseUrl = account.baseUrl?.trim();
4634
- if (!token || !baseUrl) return {
4635
- ok: false,
4636
- error: "bot token or baseUrl missing"
4637
- };
4638
- return await probeMattermost(baseUrl, token, timeoutMs);
4639
- },
4640
- buildAccountSnapshot: ({ account, runtime, probe }) => {
4641
- return {
4642
- ...buildComputedAccountStatusSnapshot({
4643
- accountId: account.accountId,
4644
- name: account.name,
4645
- enabled: account.enabled,
4646
- configured: Boolean(account.botToken && account.baseUrl),
4647
- runtime,
4648
- probe
4649
- }),
4650
- botTokenSource: account.botTokenSource,
4651
- baseUrl: account.baseUrl,
4652
- connected: runtime?.connected ?? false,
4653
- lastConnectedAt: runtime?.lastConnectedAt ?? null,
4654
- lastDisconnect: runtime?.lastDisconnect ?? null
4655
- };
4656
- }
4657
- },
4658
- gateway: { startAccount: async (ctx) => {
4659
- const account = ctx.account;
4660
- const statusSink = createAccountStatusSink({
4661
- accountId: ctx.accountId,
4662
- setStatus: ctx.setStatus
4663
- });
4664
- statusSink({
4665
- baseUrl: account.baseUrl,
4666
- botTokenSource: account.botTokenSource
4667
- });
4668
- ctx.log?.info(`[${account.accountId}] starting channel`);
4669
- return monitorMattermostProvider({
4670
- botToken: account.botToken ?? void 0,
4671
- baseUrl: account.baseUrl ?? void 0,
4672
- accountId: account.accountId,
4673
- config: ctx.cfg,
4674
- runtime: ctx.runtime,
4675
- abortSignal: ctx.abortSignal,
4676
- statusSink
4677
- });
4678
- } }
4679
- };
4680
- //#endregion
4681
- export { registerSlashCommandRoute as n, setMattermostRuntime as r, mattermostPlugin as t };