@arbium/enterprise-agent-connect 0.0.0-stage → 0.1.2-dev.75145ef

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 (322) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +269 -2
  3. package/dist/connectors/dell-techdirect/connector.json +39 -0
  4. package/dist/connectors/dell-techdirect/index.d.ts +8 -0
  5. package/dist/connectors/dell-techdirect/index.js +7 -0
  6. package/dist/connectors/dell-techdirect/index.js.map +1 -0
  7. package/dist/connectors/dell-techdirect/tools.d.ts +2 -0
  8. package/dist/connectors/dell-techdirect/tools.js +49 -0
  9. package/dist/connectors/dell-techdirect/tools.js.map +1 -0
  10. package/dist/connectors/dell-techdirect/vendor.d.ts +6 -0
  11. package/dist/connectors/dell-techdirect/vendor.js +57 -0
  12. package/dist/connectors/dell-techdirect/vendor.js.map +1 -0
  13. package/dist/connectors/dynamics-365/connector.json +228 -0
  14. package/dist/connectors/dynamics-365/index.d.ts +8 -0
  15. package/dist/connectors/dynamics-365/index.js +10 -0
  16. package/dist/connectors/dynamics-365/index.js.map +1 -0
  17. package/dist/connectors/dynamics-365/tools.d.ts +35 -0
  18. package/dist/connectors/dynamics-365/tools.js +806 -0
  19. package/dist/connectors/dynamics-365/tools.js.map +1 -0
  20. package/dist/connectors/dynamics-365/vendor.d.ts +9 -0
  21. package/dist/connectors/dynamics-365/vendor.js +110 -0
  22. package/dist/connectors/dynamics-365/vendor.js.map +1 -0
  23. package/dist/connectors/entra/connector.json +85 -0
  24. package/dist/connectors/entra/index.d.ts +2 -0
  25. package/dist/connectors/entra/index.js +4 -0
  26. package/dist/connectors/entra/index.js.map +1 -0
  27. package/dist/connectors/entra/tools.d.ts +14 -0
  28. package/dist/connectors/entra/tools.js +262 -0
  29. package/dist/connectors/entra/tools.js.map +1 -0
  30. package/dist/connectors/fedex/connector.json +118 -0
  31. package/dist/connectors/fedex/index.d.ts +8 -0
  32. package/dist/connectors/fedex/index.js +7 -0
  33. package/dist/connectors/fedex/index.js.map +1 -0
  34. package/dist/connectors/fedex/tools.d.ts +3 -0
  35. package/dist/connectors/fedex/tools.js +152 -0
  36. package/dist/connectors/fedex/tools.js.map +1 -0
  37. package/dist/connectors/fedex/vendor.d.ts +7 -0
  38. package/dist/connectors/fedex/vendor.js +98 -0
  39. package/dist/connectors/fedex/vendor.js.map +1 -0
  40. package/dist/connectors/gong/connector.json +127 -0
  41. package/dist/connectors/gong/index.d.ts +8 -0
  42. package/dist/connectors/gong/index.js +7 -0
  43. package/dist/connectors/gong/index.js.map +1 -0
  44. package/dist/connectors/gong/tools.d.ts +8 -0
  45. package/dist/connectors/gong/tools.js +424 -0
  46. package/dist/connectors/gong/tools.js.map +1 -0
  47. package/dist/connectors/gong/vendor.d.ts +4 -0
  48. package/dist/connectors/gong/vendor.js +89 -0
  49. package/dist/connectors/gong/vendor.js.map +1 -0
  50. package/dist/connectors/http/index.d.ts +34 -0
  51. package/dist/connectors/http/index.js +325 -0
  52. package/dist/connectors/http/index.js.map +1 -0
  53. package/dist/connectors/intune/connector.json +106 -0
  54. package/dist/connectors/intune/index.d.ts +2 -0
  55. package/dist/connectors/intune/index.js +4 -0
  56. package/dist/connectors/intune/index.js.map +1 -0
  57. package/dist/connectors/intune/tools.d.ts +12 -0
  58. package/dist/connectors/intune/tools.js +416 -0
  59. package/dist/connectors/intune/tools.js.map +1 -0
  60. package/dist/connectors/jamf/connector.json +182 -0
  61. package/dist/connectors/jamf/index.d.ts +8 -0
  62. package/dist/connectors/jamf/index.js +7 -0
  63. package/dist/connectors/jamf/index.js.map +1 -0
  64. package/dist/connectors/jamf/tools.d.ts +7 -0
  65. package/dist/connectors/jamf/tools.js +659 -0
  66. package/dist/connectors/jamf/tools.js.map +1 -0
  67. package/dist/connectors/jamf/vendor.d.ts +5 -0
  68. package/dist/connectors/jamf/vendor.js +70 -0
  69. package/dist/connectors/jamf/vendor.js.map +1 -0
  70. package/dist/connectors/jira/connector.json +272 -0
  71. package/dist/connectors/jira/index.d.ts +8 -0
  72. package/dist/connectors/jira/index.js +7 -0
  73. package/dist/connectors/jira/index.js.map +1 -0
  74. package/dist/connectors/jira/jql.d.ts +4 -0
  75. package/dist/connectors/jira/jql.js +67 -0
  76. package/dist/connectors/jira/jql.js.map +1 -0
  77. package/dist/connectors/jira/tools.d.ts +3 -0
  78. package/dist/connectors/jira/tools.js +464 -0
  79. package/dist/connectors/jira/tools.js.map +1 -0
  80. package/dist/connectors/jira/vendor.d.ts +3 -0
  81. package/dist/connectors/jira/vendor.js +100 -0
  82. package/dist/connectors/jira/vendor.js.map +1 -0
  83. package/dist/connectors/microsoft/client.d.ts +66 -0
  84. package/dist/connectors/microsoft/client.js +452 -0
  85. package/dist/connectors/microsoft/client.js.map +1 -0
  86. package/dist/connectors/microsoft/provider.json +118 -0
  87. package/dist/connectors/netsuite/connector.json +317 -0
  88. package/dist/connectors/netsuite/index.d.ts +8 -0
  89. package/dist/connectors/netsuite/index.js +7 -0
  90. package/dist/connectors/netsuite/index.js.map +1 -0
  91. package/dist/connectors/netsuite/tools.d.ts +24 -0
  92. package/dist/connectors/netsuite/tools.js +447 -0
  93. package/dist/connectors/netsuite/tools.js.map +1 -0
  94. package/dist/connectors/netsuite/vendor.d.ts +26 -0
  95. package/dist/connectors/netsuite/vendor.js +247 -0
  96. package/dist/connectors/netsuite/vendor.js.map +1 -0
  97. package/dist/connectors/odata/index.d.ts +33 -0
  98. package/dist/connectors/odata/index.js +460 -0
  99. package/dist/connectors/odata/index.js.map +1 -0
  100. package/dist/connectors/okta/connector.json +194 -0
  101. package/dist/connectors/okta/index.d.ts +8 -0
  102. package/dist/connectors/okta/index.js +7 -0
  103. package/dist/connectors/okta/index.js.map +1 -0
  104. package/dist/connectors/okta/tools.d.ts +6 -0
  105. package/dist/connectors/okta/tools.js +429 -0
  106. package/dist/connectors/okta/tools.js.map +1 -0
  107. package/dist/connectors/okta/vendor.d.ts +14 -0
  108. package/dist/connectors/okta/vendor.js +204 -0
  109. package/dist/connectors/okta/vendor.js.map +1 -0
  110. package/dist/connectors/outlook-calendar/connector.json +136 -0
  111. package/dist/connectors/outlook-calendar/index.d.ts +2 -0
  112. package/dist/connectors/outlook-calendar/index.js +4 -0
  113. package/dist/connectors/outlook-calendar/index.js.map +1 -0
  114. package/dist/connectors/outlook-calendar/tools.d.ts +65 -0
  115. package/dist/connectors/outlook-calendar/tools.js +398 -0
  116. package/dist/connectors/outlook-calendar/tools.js.map +1 -0
  117. package/dist/connectors/outlook-mail/connector.json +154 -0
  118. package/dist/connectors/outlook-mail/index.d.ts +2 -0
  119. package/dist/connectors/outlook-mail/index.js +4 -0
  120. package/dist/connectors/outlook-mail/index.js.map +1 -0
  121. package/dist/connectors/outlook-mail/tools.d.ts +8 -0
  122. package/dist/connectors/outlook-mail/tools.js +236 -0
  123. package/dist/connectors/outlook-mail/tools.js.map +1 -0
  124. package/dist/connectors/salesforce/connector.json +122 -0
  125. package/dist/connectors/salesforce/index.d.ts +8 -0
  126. package/dist/connectors/salesforce/index.js +7 -0
  127. package/dist/connectors/salesforce/index.js.map +1 -0
  128. package/dist/connectors/salesforce/soql.d.ts +3 -0
  129. package/dist/connectors/salesforce/soql.js +60 -0
  130. package/dist/connectors/salesforce/soql.js.map +1 -0
  131. package/dist/connectors/salesforce/tools.d.ts +9 -0
  132. package/dist/connectors/salesforce/tools.js +234 -0
  133. package/dist/connectors/salesforce/tools.js.map +1 -0
  134. package/dist/connectors/salesforce/vendor.d.ts +3 -0
  135. package/dist/connectors/salesforce/vendor.js +113 -0
  136. package/dist/connectors/salesforce/vendor.js.map +1 -0
  137. package/dist/connectors/sap-s4hana/connector.json +264 -0
  138. package/dist/connectors/sap-s4hana/index.d.ts +8 -0
  139. package/dist/connectors/sap-s4hana/index.js +7 -0
  140. package/dist/connectors/sap-s4hana/index.js.map +1 -0
  141. package/dist/connectors/sap-s4hana/tools.d.ts +4 -0
  142. package/dist/connectors/sap-s4hana/tools.js +761 -0
  143. package/dist/connectors/sap-s4hana/tools.js.map +1 -0
  144. package/dist/connectors/sap-s4hana/vendor.d.ts +5 -0
  145. package/dist/connectors/sap-s4hana/vendor.js +176 -0
  146. package/dist/connectors/sap-s4hana/vendor.js.map +1 -0
  147. package/dist/connectors/servicenow/connector.json +228 -0
  148. package/dist/connectors/servicenow/index.d.ts +8 -0
  149. package/dist/connectors/servicenow/index.js +7 -0
  150. package/dist/connectors/servicenow/index.js.map +1 -0
  151. package/dist/connectors/servicenow/tools.d.ts +4 -0
  152. package/dist/connectors/servicenow/tools.js +884 -0
  153. package/dist/connectors/servicenow/tools.js.map +1 -0
  154. package/dist/connectors/servicenow/vendor.d.ts +3 -0
  155. package/dist/connectors/servicenow/vendor.js +106 -0
  156. package/dist/connectors/servicenow/vendor.js.map +1 -0
  157. package/dist/connectors/sharepoint-files/connector.json +194 -0
  158. package/dist/connectors/sharepoint-files/excel.d.ts +16 -0
  159. package/dist/connectors/sharepoint-files/excel.js +300 -0
  160. package/dist/connectors/sharepoint-files/excel.js.map +1 -0
  161. package/dist/connectors/sharepoint-files/index.d.ts +8 -0
  162. package/dist/connectors/sharepoint-files/index.js +12 -0
  163. package/dist/connectors/sharepoint-files/index.js.map +1 -0
  164. package/dist/connectors/sharepoint-files/tools.d.ts +52 -0
  165. package/dist/connectors/sharepoint-files/tools.js +365 -0
  166. package/dist/connectors/sharepoint-files/tools.js.map +1 -0
  167. package/dist/connectors/sharepoint-files/vendor.d.ts +11 -0
  168. package/dist/connectors/sharepoint-files/vendor.js +167 -0
  169. package/dist/connectors/sharepoint-files/vendor.js.map +1 -0
  170. package/dist/connectors/slack/connector.json +148 -0
  171. package/dist/connectors/slack/index.d.ts +8 -0
  172. package/dist/connectors/slack/index.js +7 -0
  173. package/dist/connectors/slack/index.js.map +1 -0
  174. package/dist/connectors/slack/tools.d.ts +3 -0
  175. package/dist/connectors/slack/tools.js +355 -0
  176. package/dist/connectors/slack/tools.js.map +1 -0
  177. package/dist/connectors/slack/vendor.d.ts +17 -0
  178. package/dist/connectors/slack/vendor.js +189 -0
  179. package/dist/connectors/slack/vendor.js.map +1 -0
  180. package/dist/connectors/soap/index.d.ts +16 -0
  181. package/dist/connectors/soap/index.js +248 -0
  182. package/dist/connectors/soap/index.js.map +1 -0
  183. package/dist/connectors/soap/xml.d.ts +12 -0
  184. package/dist/connectors/soap/xml.js +126 -0
  185. package/dist/connectors/soap/xml.js.map +1 -0
  186. package/dist/connectors/sql/engines.d.ts +58 -0
  187. package/dist/connectors/sql/engines.js +292 -0
  188. package/dist/connectors/sql/engines.js.map +1 -0
  189. package/dist/connectors/sql/index.d.ts +16 -0
  190. package/dist/connectors/sql/index.js +214 -0
  191. package/dist/connectors/sql/index.js.map +1 -0
  192. package/dist/connectors/teams/connector.json +298 -0
  193. package/dist/connectors/teams/index.d.ts +2 -0
  194. package/dist/connectors/teams/index.js +5 -0
  195. package/dist/connectors/teams/index.js.map +1 -0
  196. package/dist/connectors/teams/tools.d.ts +7 -0
  197. package/dist/connectors/teams/tools.js +392 -0
  198. package/dist/connectors/teams/tools.js.map +1 -0
  199. package/dist/connectors/workday/connector.json +188 -0
  200. package/dist/connectors/workday/index.d.ts +8 -0
  201. package/dist/connectors/workday/index.js +7 -0
  202. package/dist/connectors/workday/index.js.map +1 -0
  203. package/dist/connectors/workday/tools.d.ts +33 -0
  204. package/dist/connectors/workday/tools.js +410 -0
  205. package/dist/connectors/workday/tools.js.map +1 -0
  206. package/dist/connectors/workday/vendor.d.ts +18 -0
  207. package/dist/connectors/workday/vendor.js +199 -0
  208. package/dist/connectors/workday/vendor.js.map +1 -0
  209. package/dist/packages/connector-kit/src/branding.d.ts +57 -0
  210. package/dist/packages/connector-kit/src/branding.js +102 -0
  211. package/dist/packages/connector-kit/src/branding.js.map +1 -0
  212. package/dist/packages/connector-kit/src/certificate.d.ts +19 -0
  213. package/dist/packages/connector-kit/src/certificate.js +84 -0
  214. package/dist/packages/connector-kit/src/certificate.js.map +1 -0
  215. package/dist/packages/connector-kit/src/common.d.ts +36 -0
  216. package/dist/packages/connector-kit/src/common.js +110 -0
  217. package/dist/packages/connector-kit/src/common.js.map +1 -0
  218. package/dist/packages/connector-kit/src/connector.d.ts +1356 -0
  219. package/dist/packages/connector-kit/src/connector.js +1073 -0
  220. package/dist/packages/connector-kit/src/connector.js.map +1 -0
  221. package/dist/packages/connector-kit/src/fetch.d.ts +6 -0
  222. package/dist/packages/connector-kit/src/fetch.js +44 -0
  223. package/dist/packages/connector-kit/src/fetch.js.map +1 -0
  224. package/dist/packages/connector-kit/src/index.d.ts +5 -0
  225. package/dist/packages/connector-kit/src/index.js +6 -0
  226. package/dist/packages/connector-kit/src/index.js.map +1 -0
  227. package/dist/packages/connector-kit/src/oauth.d.ts +15 -0
  228. package/dist/packages/connector-kit/src/oauth.js +98 -0
  229. package/dist/packages/connector-kit/src/oauth.js.map +1 -0
  230. package/dist/packages/connector-kit/src/types.d.ts +87 -0
  231. package/dist/packages/connector-kit/src/types.js +20 -0
  232. package/dist/packages/connector-kit/src/types.js.map +1 -0
  233. package/dist/packages/server/migrations/0001_tables.sql +268 -0
  234. package/dist/packages/server/migrations/0002_organizations.sql +72 -0
  235. package/dist/packages/server/migrations/0003_drop_unused.sql +3 -0
  236. package/dist/packages/server/migrations/0004_trigger_runners.sql +15 -0
  237. package/dist/packages/server/migrations/0005_run_runner.sql +3 -0
  238. package/dist/packages/server/migrations/0006_approvals.sql +36 -0
  239. package/dist/packages/server/migrations/0007_app_events.sql +110 -0
  240. package/dist/packages/server/migrations/rollback/to-legacy.sql +90 -0
  241. package/dist/packages/server/src/api.d.ts +3730 -0
  242. package/dist/packages/server/src/api.js +820 -0
  243. package/dist/packages/server/src/api.js.map +1 -0
  244. package/dist/packages/server/src/app.d.ts +23 -0
  245. package/dist/packages/server/src/app.js +467 -0
  246. package/dist/packages/server/src/app.js.map +1 -0
  247. package/dist/packages/server/src/builtin.d.ts +5 -0
  248. package/dist/packages/server/src/builtin.js +42 -0
  249. package/dist/packages/server/src/builtin.js.map +1 -0
  250. package/dist/packages/server/src/consent.d.ts +27 -0
  251. package/dist/packages/server/src/consent.js +36 -0
  252. package/dist/packages/server/src/consent.js.map +1 -0
  253. package/dist/packages/server/src/events/admin.d.ts +156 -0
  254. package/dist/packages/server/src/events/admin.js +255 -0
  255. package/dist/packages/server/src/events/admin.js.map +1 -0
  256. package/dist/packages/server/src/events/deliver.d.ts +43 -0
  257. package/dist/packages/server/src/events/deliver.js +129 -0
  258. package/dist/packages/server/src/events/deliver.js.map +1 -0
  259. package/dist/packages/server/src/events/runtime.d.ts +28 -0
  260. package/dist/packages/server/src/events/runtime.js +404 -0
  261. package/dist/packages/server/src/events/runtime.js.map +1 -0
  262. package/dist/packages/server/src/events/store.d.ts +172 -0
  263. package/dist/packages/server/src/events/store.js +275 -0
  264. package/dist/packages/server/src/events/store.js.map +1 -0
  265. package/dist/packages/server/src/index.d.ts +40 -0
  266. package/dist/packages/server/src/index.js +186 -0
  267. package/dist/packages/server/src/index.js.map +1 -0
  268. package/dist/packages/server/src/migrate.d.ts +11 -0
  269. package/dist/packages/server/src/migrate.js +134 -0
  270. package/dist/packages/server/src/migrate.js.map +1 -0
  271. package/dist/packages/server/src/openapi.d.ts +4 -0
  272. package/dist/packages/server/src/openapi.js +101 -0
  273. package/dist/packages/server/src/openapi.js.map +1 -0
  274. package/dist/packages/server/src/queue.d.ts +10 -0
  275. package/dist/packages/server/src/queue.js +45 -0
  276. package/dist/packages/server/src/queue.js.map +1 -0
  277. package/dist/packages/server/src/secrets.d.ts +19 -0
  278. package/dist/packages/server/src/secrets.js +76 -0
  279. package/dist/packages/server/src/secrets.js.map +1 -0
  280. package/dist/packages/server/src/server.d.ts +1 -0
  281. package/dist/packages/server/src/server.js +82 -0
  282. package/dist/packages/server/src/server.js.map +1 -0
  283. package/dist/packages/server/src/service.d.ts +767 -0
  284. package/dist/packages/server/src/service.js +924 -0
  285. package/dist/packages/server/src/service.js.map +1 -0
  286. package/dist/packages/server/src/store.d.ts +157 -0
  287. package/dist/packages/server/src/store.js +415 -0
  288. package/dist/packages/server/src/store.js.map +1 -0
  289. package/dist/packages/server/src/teams/activity.d.ts +123 -0
  290. package/dist/packages/server/src/teams/activity.js +188 -0
  291. package/dist/packages/server/src/teams/activity.js.map +1 -0
  292. package/dist/packages/server/src/teams/admin.d.ts +335 -0
  293. package/dist/packages/server/src/teams/admin.js +699 -0
  294. package/dist/packages/server/src/teams/admin.js.map +1 -0
  295. package/dist/packages/server/src/teams/approval.d.ts +57 -0
  296. package/dist/packages/server/src/teams/approval.js +118 -0
  297. package/dist/packages/server/src/teams/approval.js.map +1 -0
  298. package/dist/packages/server/src/teams/bot.d.ts +25 -0
  299. package/dist/packages/server/src/teams/bot.js +124 -0
  300. package/dist/packages/server/src/teams/bot.js.map +1 -0
  301. package/dist/packages/server/src/teams/claude.d.ts +46 -0
  302. package/dist/packages/server/src/teams/claude.js +165 -0
  303. package/dist/packages/server/src/teams/claude.js.map +1 -0
  304. package/dist/packages/server/src/teams/jwt.d.ts +17 -0
  305. package/dist/packages/server/src/teams/jwt.js +121 -0
  306. package/dist/packages/server/src/teams/jwt.js.map +1 -0
  307. package/dist/packages/server/src/teams/package.d.ts +79 -0
  308. package/dist/packages/server/src/teams/package.js +319 -0
  309. package/dist/packages/server/src/teams/package.js.map +1 -0
  310. package/dist/packages/server/src/teams/provision.d.ts +84 -0
  311. package/dist/packages/server/src/teams/provision.js +505 -0
  312. package/dist/packages/server/src/teams/provision.js.map +1 -0
  313. package/dist/packages/server/src/teams/runner.d.ts +80 -0
  314. package/dist/packages/server/src/teams/runner.js +187 -0
  315. package/dist/packages/server/src/teams/runner.js.map +1 -0
  316. package/dist/packages/server/src/teams/runtime.d.ts +91 -0
  317. package/dist/packages/server/src/teams/runtime.js +679 -0
  318. package/dist/packages/server/src/teams/runtime.js.map +1 -0
  319. package/dist/packages/server/src/teams/store.d.ts +233 -0
  320. package/dist/packages/server/src/teams/store.js +507 -0
  321. package/dist/packages/server/src/teams/store.js.map +1 -0
  322. package/package.json +68 -4
@@ -0,0 +1,699 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { brand } from "../../../connector-kit/src/branding.js";
4
+ import { describeCertificate, newCertificate } from "../../../connector-kit/src/certificate.js";
5
+ import { signInNonce, signState, stateApp, verifyState } from "../consent.js";
6
+ import { credentialAad } from "../secrets.js";
7
+ import { InvalidInput } from "../service.js";
8
+ import { SHARED_APP_AUTH } from "../../../../connectors/microsoft/client.js";
9
+ import { ConnectError } from "../../../connector-kit/src/types.js";
10
+ import { agentMcpUrl } from "./claude.js";
11
+ import { packageZip, removalScript, setupScript } from "./package.js";
12
+ import { CATALOG_SCOPE, LIVE_CHECKS, azureTarget, chatIdFrom, checkSignIn, deprovisionBot, installBot, provisionBot, publishPackage, searchTargets, setupChecks, } from "./provision.js";
13
+ import { botAad, botCredential } from "./runtime.js";
14
+ import { CLAUDE_RUNNER, openTriggerSecret, triggerAad } from "./runner.js";
15
+ // The admin side of Teams: each agent's bot (made, proven, rotated, removed), its package and
16
+ // setup script, its real-time settings and recent responses, and the organization's Claude
17
+ // API key. docs/teams.md 9.
18
+ const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
19
+ const CLAUDE_SCOPE = "provider:anthropic";
20
+ /** The setup script passes the bot's name to az, which runs through cmd.exe on Windows, where
21
+ * quoting cannot make & " | ^ % < > inert; so only characters inert there are allowed. */
22
+ export const BOT_NAME = /^[A-Za-z0-9 ._-]{1,30}$/;
23
+ export const triggerSchema = z
24
+ .object({
25
+ enabled: z.boolean(),
26
+ // What answers (runner.ts); Claude Managed Agents unless named.
27
+ runner: z
28
+ .string()
29
+ .regex(/^[a-z][a-z0-9-]{1,40}$/)
30
+ .default(CLAUDE_RUNNER),
31
+ // Claude Managed Agents' settings, kept where they were before there were other runners.
32
+ managedAgentId: z
33
+ .string()
34
+ .regex(/^[A-Za-z0-9_-]{1,128}$/)
35
+ .optional(),
36
+ environmentId: z
37
+ .string()
38
+ .regex(/^[A-Za-z0-9_-]{1,128}$/)
39
+ .optional(),
40
+ vaultIds: z
41
+ .array(z.string().regex(/^[A-Za-z0-9_-]{1,128}$/))
42
+ .max(20)
43
+ .default([]),
44
+ budgetCents: z.number().int().min(1).max(2000),
45
+ maxRunsPerHour: z.number().int().min(1).max(100).default(20),
46
+ maxCentsPerDay: z.number().int().min(1).max(100_000).default(500),
47
+ maxConcurrent: z.number().int().min(1).max(10).default(2),
48
+ replyMode: z.enum(["mentions", "all"]).default("mentions"),
49
+ allowedTeams: z.array(z.string().regex(GUID)).max(500).default([]),
50
+ allowedPeople: z.array(z.string().regex(GUID)).max(500).default([]),
51
+ // Another runner's settings (e.g. the signed webhook's url), which that runner checks.
52
+ settings: z.record(z.string(), z.unknown()).default({}),
53
+ // The runner's signing secret (the signed webhook's): never shown again, kept until a
54
+ // new one is given.
55
+ signingSecret: z.string().min(32).max(256).optional(),
56
+ // Whoever may ask gets everything the agent may do, so "anyone" is a choice, never a default.
57
+ anyoneInOrganization: z.boolean().default(false),
58
+ by: z.string().trim().min(1).max(200),
59
+ })
60
+ .refine((t) => !t.enabled || t.anyoneInOrganization || t.allowedPeople.length > 0, {
61
+ message: "Name the people who may ask it, or allow anyone in your organization",
62
+ path: ["allowedPeople"],
63
+ })
64
+ .refine((t) => t.maxCentsPerDay >= t.budgetCents, {
65
+ message: "The daily limit must be at least the limit per answer",
66
+ path: ["maxCentsPerDay"],
67
+ });
68
+ const certificateInfo = (pem) => (pem ? describeCertificate(pem) : null);
69
+ /** A trigger as the admin sees it: whether it has a signing secret, never the secret. */
70
+ function publicTrigger(row) {
71
+ const { secret, secretKeyId: _k, ...rest } = row;
72
+ return { ...rest, hasSigningSecret: secret !== null };
73
+ }
74
+ const packageVersion = (bot) => `1.0.${bot.manifestVersion}`;
75
+ function publicBot(bot) {
76
+ const { credential: _c, credentialKeyId: _k, config, ...rest } = bot;
77
+ return {
78
+ ...rest,
79
+ certificate: certificateInfo(config.certificate),
80
+ nextCertificate: certificateInfo(config.nextCertificate),
81
+ // Made by Add to Teams (so removed by the service too), not by the setup script.
82
+ madeByService: Boolean(config.appObjectId),
83
+ // The package version in the Teams app catalog, once published from here.
84
+ publishedVersion: config.publishedVersion ?? null,
85
+ packageVersion: packageVersion(bot),
86
+ };
87
+ }
88
+ const PUBLISH_APP = /^teams-publish:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
89
+ export function createTeamsAdmin(deps) {
90
+ const { store, teams, key } = deps;
91
+ const stateKey = key.derive("teams-publish-state");
92
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
93
+ // The Microsoft connection, signing in as the customer's own app (branding.microsoft.appName).
94
+ async function microsoftFor(org) {
95
+ const row = await store.getProvider(org, "microsoft");
96
+ if (!row || row.status === "pending")
97
+ throw new ConnectError("Connect Microsoft 365 first");
98
+ if (row.config.auth === SHARED_APP_AUTH)
99
+ throw new ConnectError("Add to Teams needs your own app registration for Microsoft 365. Use the setup script instead.");
100
+ const conn = {
101
+ config: row.config,
102
+ secret: key.open({ ciphertext: row.credential, keyId: row.credentialKeyId }, credentialAad(org, row.credentialScope)),
103
+ };
104
+ const call = (request, origin) => deps.microsoft.call(conn, request, origin);
105
+ return { config: row.config, conn, call };
106
+ }
107
+ // Bot config changes (what Add to Teams made, what was published) over the credential read.
108
+ async function saveConfig(b, config) {
109
+ if (!(await teams.updateBotCredential(b.id, config, b.credential, b.credentialKeyId, b.credential)))
110
+ throw new ConnectError("This bot changed meanwhile. Reload the page.");
111
+ }
112
+ // The Teams page's opt-in to adding bots for one person (its own permission).
113
+ const personInstalls = async (org) => (await store.getConnection(org, "teams"))?.config.person_installs === "on";
114
+ // Its choice of making and adding bots here (the permissions for that), or only scripts.
115
+ const addsBots = async (org) => (await store.getConnection(org, "teams"))?.config.add_to_teams !== "off";
116
+ const addsOff = () => `Making and adding bots is off. Choose "${brand().productName} makes and adds them" in step 1 of the Microsoft Teams page and add the permissions it shows, or use the setup script.`;
117
+ const publicUrl = () => {
118
+ if (!deps.publicUrl)
119
+ throw new ConnectError("This deployment has no public address for Teams to reach. Set global.publicBaseUrl in its settings.");
120
+ return deps.publicUrl;
121
+ };
122
+ async function log(org, by, action, target) {
123
+ await store.audit({
124
+ organizationId: org,
125
+ agentId: null,
126
+ agentName: by,
127
+ connectorId: "teams",
128
+ tool: `admin:${action}`,
129
+ target,
130
+ decision: "allowed",
131
+ reason: null,
132
+ vendorStatus: null,
133
+ });
134
+ }
135
+ async function bot(org, agentId) {
136
+ const found = await teams.botForAgent(org, agentId);
137
+ if (!found)
138
+ throw new InvalidInput("This agent has no Teams bot yet");
139
+ return found;
140
+ }
141
+ const open = (b) => key.open({ ciphertext: b.credential, keyId: b.credentialKeyId }, botAad(b.organizationId, b.id));
142
+ async function claudeFor(org) {
143
+ const row = await teams.claudeKey(org);
144
+ if (!row)
145
+ throw new ConnectError("Set your organization's Claude API key first");
146
+ const { api_key } = key.open({ ciphertext: row.credential, keyId: row.credentialKeyId }, credentialAad(org, CLAUDE_SCOPE));
147
+ return deps.claude(api_key);
148
+ }
149
+ // Live sessions are stopped before what they use goes; `runner`: only that runner's.
150
+ async function stopSessions(org, which, runner) {
151
+ const bots = new Set((await teams.listBots(org)).map((b) => b.id));
152
+ const live = (await teams.runningRuns()).filter((r) => bots.has(r.botId) && which(r) && (!runner || r.runner === runner));
153
+ for (const run of live) {
154
+ const answers = deps.runners.get(run.runner);
155
+ if (answers)
156
+ await answers.stop(run.sessionId, org);
157
+ await teams.updateRun(run.id, { state: "failed", error: "Stopped by an administrator" });
158
+ }
159
+ }
160
+ return {
161
+ async listBots(org) {
162
+ const rows = await teams.listBots(org);
163
+ const places = await teams.conversationsOf(rows.map((b) => b.id));
164
+ // Where Teams sends this bot's messages, and the MCP address its Claude vault holds a key
165
+ // for: shown to the admin for setting up by hand.
166
+ const base = deps.publicUrl;
167
+ return rows.map((b) => ({
168
+ ...publicBot(b),
169
+ messagingEndpoint: base
170
+ ? `${base}/teams/bots/${b.botAppId ?? "<application ID>"}/messages`
171
+ : null,
172
+ mcpUrl: base ? agentMcpUrl(base, b.agentId) : null,
173
+ installedIn: places
174
+ .filter((p) => p.botId === b.id)
175
+ .map((p) => ({
176
+ kind: p.kind,
177
+ name: p.name,
178
+ id: p.teamGroupId ?? p.conversationId,
179
+ conversationId: p.conversationId,
180
+ })),
181
+ }));
182
+ },
183
+ // A key pair for the agent's bot, kept until the admin runs the setup script and pastes
184
+ // the app id it printed. The tenant is the Microsoft connection's.
185
+ async createBot(org, agentId, displayName, by) {
186
+ const name = displayName.trim();
187
+ if (!BOT_NAME.test(name))
188
+ throw new InvalidInput("A bot name is 1 to 30 letters, digits, spaces, dots, dashes or underscores");
189
+ const provider = await store.getProvider(org, "microsoft");
190
+ const tenantId = provider?.status === "connected" ? provider.config.tenantId : undefined;
191
+ if (!tenantId)
192
+ throw new ConnectError("Connect Microsoft 365 first");
193
+ // Two bots with one name would look the same in Teams and in the Entra app list.
194
+ const taken = (await teams.listBots(org)).find((b) => b.agentId !== agentId && b.displayName.toLowerCase() === name.toLowerCase());
195
+ if (taken)
196
+ throw new InvalidInput(`Another agent's Teams bot is already named ${name}`);
197
+ const id = randomUUID();
198
+ const made = newCertificate(`${brand().productName} agent ${name}`);
199
+ const sealed = key.seal({ private_key: made.privateKey }, botAad(org, id));
200
+ const created = await teams.createBot({
201
+ id,
202
+ organizationId: org,
203
+ agentId,
204
+ tenantId,
205
+ displayName: name,
206
+ config: { certificate: made.certificate },
207
+ credential: sealed.ciphertext,
208
+ credentialKeyId: sealed.keyId,
209
+ createdBy: by,
210
+ });
211
+ if (!created) {
212
+ if (await teams.botForAgent(org, agentId))
213
+ throw new InvalidInput("This agent already has a Teams bot");
214
+ return null;
215
+ }
216
+ await log(org, by, "create_bot", `agent ${agentId} bot ${id} "${name}"`);
217
+ return publicBot((await teams.botById(id)));
218
+ },
219
+ async setupScript(org, agentId) {
220
+ const b = await bot(org, agentId);
221
+ return setupScript({
222
+ botId: b.id,
223
+ displayName: b.displayName,
224
+ teamsAppId: b.teamsAppId,
225
+ tenantId: b.tenantId,
226
+ certificate: b.config.certificate,
227
+ publicUrl: publicUrl(),
228
+ });
229
+ },
230
+ // The app id the script printed, proven by signing in as it with the bot's key.
231
+ async connectBot(org, agentId, rawAppId, by) {
232
+ const botAppId = rawAppId.trim().toLowerCase();
233
+ if (!GUID.test(botAppId))
234
+ throw new InvalidInput("Paste the application ID the script printed");
235
+ const b = await bot(org, agentId);
236
+ if (b.botAppId && b.botAppId !== botAppId)
237
+ throw new InvalidInput("This bot is connected to another app. Remove it and set it up again.");
238
+ const provider = await store.getProvider(org, "microsoft");
239
+ if (provider?.config.clientId === botAppId)
240
+ throw new InvalidInput("That is the Microsoft connection's app, not the agent's bot");
241
+ await deps.bots.prove({
242
+ botAppId,
243
+ tenantId: b.tenantId,
244
+ certificate: b.config.certificate,
245
+ privateKey: open(b).private_key,
246
+ });
247
+ if (!(await teams.connectBot(b.id, botAppId, b.credential)))
248
+ throw new ConnectError("Another bot already uses this app, or this one changed meanwhile. Reload the page.");
249
+ await log(org, by, "connect_bot", `bot ${b.id} app ${botAppId}`);
250
+ return publicBot((await teams.botById(b.id)));
251
+ },
252
+ async packageZip(org, agentId) {
253
+ const b = await bot(org, agentId);
254
+ if (!b.botAppId)
255
+ throw new InvalidInput("Finish setting up the bot first");
256
+ return packageZip({
257
+ displayName: b.displayName,
258
+ teamsAppId: b.teamsAppId,
259
+ botAppId: b.botAppId,
260
+ manifestVersion: b.manifestVersion,
261
+ publicUrl: publicUrl(),
262
+ });
263
+ },
264
+ // A certificate to rotate to: the admin appends it to the bot's app, then switches.
265
+ async newCertificate(org, agentId, by) {
266
+ const b = await bot(org, agentId);
267
+ const made = newCertificate(`${brand().productName} agent ${b.displayName}`);
268
+ const sealed = key.seal({ ...open(b), next_private_key: made.privateKey }, botAad(org, b.id));
269
+ const saved = await teams.updateBotCredential(b.id, { ...b.config, nextCertificate: made.certificate }, sealed.ciphertext, sealed.keyId, b.credential);
270
+ if (!saved)
271
+ throw new ConnectError("This bot changed meanwhile. Reload the page.");
272
+ await log(org, by, "create_certificate", `bot ${b.id} next ${describeCertificate(made.certificate).thumbprint}`);
273
+ return {
274
+ bot: publicBot((await teams.botById(b.id))),
275
+ // The line that adds it to the bot's app, keeping the current one.
276
+ command: b.botAppId
277
+ ? `az ad app credential reset --id ${b.botAppId} --cert "@next.pem" --append`
278
+ : null,
279
+ certificate: made.certificate,
280
+ };
281
+ },
282
+ // Signs in with the new certificate and, only if that works, switches to it.
283
+ async activateCertificate(org, agentId, by) {
284
+ const b = await bot(org, agentId);
285
+ const { nextCertificate, ...config } = b.config;
286
+ const { next_private_key, ...secret } = open(b);
287
+ if (!nextCertificate || !next_private_key)
288
+ throw new InvalidInput("There is no new certificate to switch to");
289
+ if (b.botAppId)
290
+ await deps.bots.prove({
291
+ botAppId: b.botAppId,
292
+ tenantId: b.tenantId,
293
+ certificate: nextCertificate,
294
+ privateKey: next_private_key,
295
+ });
296
+ const sealed = key.seal({ ...secret, private_key: next_private_key }, botAad(org, b.id));
297
+ const saved = await teams.updateBotCredential(b.id, { ...config, certificate: nextCertificate }, sealed.ciphertext, sealed.keyId, b.credential);
298
+ if (!saved)
299
+ throw new ConnectError("This bot changed meanwhile. Reload the page.");
300
+ await log(org, by, "switch_certificate", `bot ${b.id} ${describeCertificate(b.config.certificate).thumbprint} -> ${describeCertificate(nextCertificate).thumbprint}`);
301
+ return publicBot((await teams.botById(b.id)));
302
+ },
303
+ // Signs in again, as the integrations page does for other connections.
304
+ async checkBot(org, agentId) {
305
+ const b = await bot(org, agentId);
306
+ if (!b.botAppId)
307
+ return { ok: false, error: "Not set up yet" };
308
+ try {
309
+ await deps.bots.prove(botCredential(key, b));
310
+ await teams.markBot(b.id, true);
311
+ return { ok: true };
312
+ }
313
+ catch (error) {
314
+ if (!(error instanceof ConnectError))
315
+ throw error;
316
+ await teams.markBot(b.id, false);
317
+ return { ok: false, error: error.message };
318
+ }
319
+ },
320
+ // The certificate's public half, for uploading to the app registration by hand.
321
+ async certificate(org, agentId) {
322
+ return (await bot(org, agentId)).config.certificate;
323
+ },
324
+ // A short message from the bot in one place it was added to, proving the whole path.
325
+ async sendTest(org, agentId, conversationId, by) {
326
+ const b = await bot(org, agentId);
327
+ if (b.status !== "connected")
328
+ throw new InvalidInput("Finish setting up the bot first");
329
+ const place = (await teams.conversations(b.id)).find((p) => p.conversationId === conversationId);
330
+ if (!place)
331
+ throw new InvalidInput("The bot is not in that conversation any more");
332
+ const res = await deps.bots.connector(botCredential(key, b), place.serviceUrl, {
333
+ method: "POST",
334
+ path: `/v3/conversations/${encodeURIComponent(place.conversationId)}/activities`,
335
+ body: {
336
+ type: "message",
337
+ text: `Test message from ${b.displayName}, sent from ${brand().productName} by ${by}.`,
338
+ },
339
+ });
340
+ if (res.status >= 300)
341
+ throw new ConnectError(`Teams did not accept the message (status ${res.status})`);
342
+ await log(org, by, "send_test", `bot ${b.id} conversation ${place.conversationId}`);
343
+ return { ok: true };
344
+ },
345
+ // Forgets the bot and its key here. What Add to Teams made is deleted with API calls; each
346
+ // one that fails is returned, with the commands that remove the rest from the tenant.
347
+ async removeBot(org, agentId, by) {
348
+ const b = await teams.botForAgent(org, agentId);
349
+ if (!b)
350
+ return null;
351
+ await stopSessions(org, (run) => run.botId === b.id);
352
+ const made = Boolean(b.config.appObjectId || b.config.azureBot);
353
+ const failed = [];
354
+ if (made)
355
+ try {
356
+ failed.push(...(await deprovisionBot((await microsoftFor(org)).call, b.config)));
357
+ }
358
+ catch (error) {
359
+ if (!(error instanceof ConnectError))
360
+ throw error;
361
+ failed.push(error.message);
362
+ }
363
+ if (!(await teams.deleteBot(org, agentId)))
364
+ return null;
365
+ await log(org, by, "remove_bot", `bot ${b.id} app ${b.botAppId ?? "-"}${made ? (failed.length ? `; not removed: ${failed.join("; ")}` : "; removed from the tenant") : ""}`);
366
+ return {
367
+ removal: removalScript({
368
+ botId: b.id,
369
+ botAppId: b.botAppId ?? b.config.appId ?? null,
370
+ teamsAppId: b.teamsAppId,
371
+ catalogOnly: made && failed.length === 0,
372
+ }),
373
+ failed,
374
+ };
375
+ },
376
+ // ---- Add to Teams ----
377
+ // The Teams page's set-up-once steps that the service checks live (`live` in teams.json).
378
+ async setupState(org) {
379
+ const row = await store.getProvider(org, "microsoft");
380
+ if (!row || row.status === "pending") {
381
+ const todo = { state: "todo", message: "Connect Microsoft 365 first." };
382
+ return Object.fromEntries(LIVE_CHECKS.map((id) => [id, todo]));
383
+ }
384
+ let microsoft;
385
+ try {
386
+ microsoft = await microsoftFor(org);
387
+ }
388
+ catch (error) {
389
+ // E.g. the shared app: reloading never helps, so each step says why.
390
+ if (!(error instanceof ConnectError))
391
+ throw error;
392
+ const failing = { state: "failing", message: error.message };
393
+ return Object.fromEntries(LIVE_CHECKS.map((id) => [id, failing]));
394
+ }
395
+ return setupChecks(microsoft.call, microsoft.config);
396
+ },
397
+ // Makes the agent's bot in the tenant: its key pair here if missing, then the app
398
+ // registration, service principal, Azure Bot and Teams channel, then a sign-in as it.
399
+ // Safe to run again: each part is made only if missing.
400
+ async addToTeams(org, agentId, displayName, by) {
401
+ if (!(await store.getConnection(org, "teams")))
402
+ throw new ConnectError("Turn on Microsoft Teams first");
403
+ if (!(await addsBots(org)))
404
+ throw new ConnectError(addsOff());
405
+ const { call, config } = await microsoftFor(org);
406
+ const target = azureTarget(config);
407
+ if (!target)
408
+ throw new ConnectError("Enter the Azure subscription and resource group on the Microsoft Teams page first");
409
+ if (!(await teams.botForAgent(org, agentId)))
410
+ if (!(await this.createBot(org, agentId, displayName, by)))
411
+ throw new InvalidInput("No such agent");
412
+ const b = (await teams.botForAgent(org, agentId));
413
+ if (b.botAppId)
414
+ throw new InvalidInput("This agent's bot is already set up");
415
+ if (b.tenantId !== config.tenantId)
416
+ throw new ConnectError("This bot was made for another Microsoft organization. Remove it and add it again.");
417
+ // Logged before any check below: by then the run may have made things in the tenant.
418
+ const result = await provisionBot({
419
+ call,
420
+ bot: {
421
+ id: b.id,
422
+ displayName: b.displayName,
423
+ tenantId: b.tenantId,
424
+ certificate: b.config.certificate,
425
+ },
426
+ config: b.config,
427
+ save: async (next) => {
428
+ try {
429
+ await saveConfig(b, next);
430
+ }
431
+ catch (error) {
432
+ if (!(error instanceof ConnectError) || (await teams.botById(b.id)))
433
+ throw error;
434
+ // Removed while this ran: nothing would record what this run made, so undo it.
435
+ const left = await deprovisionBot(call, next);
436
+ throw new ConnectError(left.length
437
+ ? `This bot was removed while it was being added. Remove what is left by hand: ${left.join("; ")}`
438
+ : "This bot was removed while it was being added, so what was made for it is deleted again.");
439
+ }
440
+ },
441
+ target,
442
+ publicUrl: publicUrl(),
443
+ prove: (botAppId) => deps.bots.prove({
444
+ botAppId,
445
+ tenantId: b.tenantId,
446
+ certificate: b.config.certificate,
447
+ privateKey: open(b).private_key,
448
+ }),
449
+ sleep,
450
+ waits: deps.waits,
451
+ }).catch(async (error) => {
452
+ const why = error instanceof Error ? error.message : String(error);
453
+ await log(org, by, "add_to_teams", `bot ${b.id} stopped: ${why.slice(0, 200)}`);
454
+ throw error;
455
+ });
456
+ await log(org, by, "add_to_teams", `bot ${b.id} ${result.steps.map((s) => `${s.id}=${s.state}`).join(" ")}`);
457
+ if (!(await teams.botById(b.id)))
458
+ throw new ConnectError(result.steps.find((x) => x.state === "failed")?.message ??
459
+ "This bot was removed while it was being added.");
460
+ if (result.botAppId && !(await teams.connectBot(b.id, result.botAppId, b.credential)))
461
+ throw new ConnectError("Another bot already uses this app, or this one changed meanwhile. Reload the page.");
462
+ return { steps: result.steps, bot: publicBot((await teams.botById(b.id))) };
463
+ },
464
+ // Where the administrator signs in to publish the bot. The nonce goes back to the admin
465
+ // app, which keeps it in this browser until the sign-in returns.
466
+ async startPublish(org, agentId, redirectUri) {
467
+ const b = await bot(org, agentId);
468
+ if (!b.botAppId || b.status !== "connected")
469
+ throw new InvalidInput("Finish adding the bot first");
470
+ const { conn } = await microsoftFor(org);
471
+ const { state, nonce } = signState(stateKey, {
472
+ org,
473
+ app: `teams-publish:${agentId}`,
474
+ ceiling: [],
475
+ redirectUri,
476
+ });
477
+ return {
478
+ url: deps.microsoft.delegated.signInUrl(conn, {
479
+ state,
480
+ redirectUri,
481
+ nonce: signInNonce(nonce),
482
+ scope: CATALOG_SCOPE,
483
+ }),
484
+ nonce,
485
+ };
486
+ },
487
+ // The sign-in came back: the signed state names the agent, the nonce must be this
488
+ // browser's, the id token this tenant's administrator. Then the package is published (a
489
+ // new version of it if one is there already) with that sign-in, never kept.
490
+ async finishPublish(org, input) {
491
+ const app = stateApp(input.state) ?? "";
492
+ const agentId = PUBLISH_APP.exec(app)?.[1];
493
+ if (!agentId)
494
+ throw new ConnectError("This sign-in did not start here. Start again from API keys.");
495
+ const state = verifyState(stateKey, input.state, { org, app, nonce: input.nonce });
496
+ const { error, error_description: description, code } = input.params;
497
+ if (error)
498
+ throw new ConnectError(`Microsoft did not sign you in: ${(description ?? error).split(/\r?\n/)[0].slice(0, 300)}`);
499
+ if (!code)
500
+ throw new ConnectError("Microsoft did not confirm the sign-in. Start again.");
501
+ const b = await bot(org, agentId);
502
+ // Removed or set up again during the sign-in.
503
+ if (b.status !== "connected")
504
+ throw new ConnectError("This bot is no longer set up. Start again from API keys.");
505
+ const { conn, config } = await microsoftFor(org);
506
+ const { accessToken, claims } = await deps.microsoft.delegated.redeem(conn, {
507
+ code,
508
+ redirectUri: state.redirectUri,
509
+ scope: CATALOG_SCOPE,
510
+ });
511
+ checkSignIn(claims, {
512
+ clientId: config.clientId,
513
+ tenantId: config.tenantId,
514
+ nonce: signInNonce(input.nonce),
515
+ });
516
+ if (!b.botAppId || b.tenantId !== config.tenantId)
517
+ throw new ConnectError("This bot is not set up in this Microsoft organization");
518
+ const version = packageVersion(b);
519
+ const published = await publishPackage({
520
+ token: accessToken,
521
+ externalId: b.teamsAppId,
522
+ version,
523
+ zip: packageZip({
524
+ displayName: b.displayName,
525
+ teamsAppId: b.teamsAppId,
526
+ botAppId: b.botAppId,
527
+ manifestVersion: b.manifestVersion,
528
+ publicUrl: publicUrl(),
529
+ }),
530
+ }, deps.fetch);
531
+ // Logged before saving: a save refused by a change meanwhile must not hide the publish.
532
+ const who = typeof claims.preferred_username === "string" ? claims.preferred_username : "-";
533
+ await log(org, input.by, "publish_bot", `bot ${b.id} catalog ${published.catalogAppId} version ${version}${published.changed ? "" : " (already there)"} signed in as ${who}`);
534
+ await saveConfig(b, {
535
+ ...b.config,
536
+ catalogAppId: published.catalogAppId,
537
+ publishedVersion: version,
538
+ });
539
+ return { agentId, version, changed: published.changed };
540
+ },
541
+ // Teams or people to add the bot to, matching what the admin typed.
542
+ async installTargets(org, agentId, kind, query) {
543
+ await bot(org, agentId);
544
+ return searchTargets((await microsoftFor(org)).call, kind, query);
545
+ },
546
+ // Adds the published bot to a team, a group chat (its id or a pasted link) or a person.
547
+ async install(org, agentId, kind, rawId, by) {
548
+ const b = await bot(org, agentId);
549
+ const catalogAppId = b.config.catalogAppId;
550
+ if (b.status !== "connected" || !catalogAppId)
551
+ throw new InvalidInput("Publish the bot to your Teams apps first");
552
+ const id = kind === "chat" ? chatIdFrom(rawId) : rawId.trim().toLowerCase();
553
+ if (!id || (kind !== "chat" && !GUID.test(id)))
554
+ throw new InvalidInput(kind === "chat"
555
+ ? "Paste the group chat's link from Teams (Copy link) or its id"
556
+ : `Pick a ${kind} from the list`);
557
+ if (!(await addsBots(org)))
558
+ throw new ConnectError(addsOff());
559
+ if (kind === "person" && (await personInstalls(org)) !== true)
560
+ throw new ConnectError('Adding a bot for one person is off. Choose "Also for one person" in step 1 of the Microsoft Teams page and add the permission it shows, or let the person add the bot in Teams.');
561
+ const { call, config } = await microsoftFor(org);
562
+ if (b.tenantId !== config.tenantId)
563
+ throw new ConnectError("This bot was made for another Microsoft organization. Remove it and add it again.");
564
+ const result = await installBot(call, catalogAppId, kind, id);
565
+ await log(org, by, "install_bot", `bot ${b.id} ${kind} ${id} ${result}`);
566
+ return { result };
567
+ },
568
+ async trigger(org, agentId) {
569
+ const b = await teams.botForAgent(org, agentId);
570
+ const row = await teams.trigger(agentId);
571
+ // Only an agent of this organization (its bot proves that, or nothing is set).
572
+ return b && row ? publicTrigger(row) : null;
573
+ },
574
+ // Proven by its runner first (for Claude: the agent and environment exist under the
575
+ // organization's key, see verifyTrigger).
576
+ async setTrigger(org, agentId, raw) {
577
+ const parsed = triggerSchema.safeParse(raw);
578
+ if (!parsed.success)
579
+ throw new InvalidInput(z.prettifyError(parsed.error));
580
+ const { by, signingSecret, settings: rawSettings, ...input } = parsed.data;
581
+ await bot(org, agentId);
582
+ const runner = deps.runners.get(input.runner);
583
+ if (!runner)
584
+ throw new InvalidInput(`This deployment offers no runner ${input.runner}`);
585
+ const settings = runner.settings.safeParse(rawSettings);
586
+ if (!settings.success)
587
+ throw new InvalidInput(z.prettifyError(settings.error));
588
+ publicUrl(); // Every runner answers through this service's MCP address.
589
+ const existing = await teams.trigger(agentId);
590
+ const sealed = signingSecret
591
+ ? key.seal({ secret: signingSecret }, triggerAad(org, agentId))
592
+ : existing?.secret
593
+ ? { ciphertext: existing.secret, keyId: existing.secretKeyId }
594
+ : null;
595
+ const row = {
596
+ agentId,
597
+ ...input,
598
+ managedAgentId: input.managedAgentId ?? null,
599
+ environmentId: input.environmentId ?? null,
600
+ settings: settings.data,
601
+ secret: sealed?.ciphertext ?? null,
602
+ secretKeyId: sealed?.keyId ?? null,
603
+ allowedTeams: input.allowedTeams.map((t) => t.toLowerCase()),
604
+ allowedPeople: input.allowedPeople.map((p) => p.toLowerCase()),
605
+ updatedBy: by,
606
+ };
607
+ const secret = signingSecret ?? (existing ? openTriggerSecret(key, org, existing) : null);
608
+ await runner.verify(row, org, secret, "save");
609
+ // Another runner from now on: the old one's live sessions are stopped while it can.
610
+ if (existing && existing.runner !== input.runner)
611
+ await stopSessions(org, (r) => r.agentId === agentId, existing.runner);
612
+ const saved = await teams.setTrigger(org, row);
613
+ if (!saved)
614
+ return null;
615
+ await log(org, by, "set_trigger", `agent ${agentId} ${input.enabled ? "on" : "off"} ${input.runner === CLAUDE_RUNNER ? `agent ${input.managedAgentId}` : `runner ${input.runner}`} budget ${input.budgetCents}c`);
616
+ return this.trigger(org, agentId);
617
+ },
618
+ async runs(org, agentId, limit) {
619
+ return teams.recentRuns(org, agentId, limit);
620
+ },
621
+ // ---- the organization's Claude API key ----
622
+ async claudeKey(org) {
623
+ const row = await teams.claudeKey(org);
624
+ return row
625
+ ? {
626
+ set: true,
627
+ ending: row.config.ending,
628
+ by: row.connectedBy,
629
+ at: row.connectedAt,
630
+ }
631
+ : { set: false };
632
+ },
633
+ // Proven before saving: listing the Managed Agents it can use. That list is also what the
634
+ // admin chooses from.
635
+ async setClaudeKey(org, rawKey, by) {
636
+ const apiKey = rawKey.trim();
637
+ if (!/^sk-ant-[A-Za-z0-9_-]{20,}$/.test(apiKey))
638
+ throw new InvalidInput("Paste a Claude API key (it starts with sk-ant-)");
639
+ const agents = await deps.claude(apiKey).listAgents();
640
+ const sealed = key.seal({ api_key: apiKey }, credentialAad(org, CLAUDE_SCOPE));
641
+ await teams.setClaudeKey({
642
+ organizationId: org,
643
+ config: { ending: apiKey.slice(-4) },
644
+ credential: sealed.ciphertext,
645
+ credentialKeyId: sealed.keyId,
646
+ credentialScope: CLAUDE_SCOPE,
647
+ connectedBy: by,
648
+ });
649
+ await log(org, by, "set_claude_key", `ending ${apiKey.slice(-4)}`);
650
+ const saved = await this.claudeKey(org);
651
+ if (!saved.set)
652
+ throw new Error("The Claude key vanished after saving");
653
+ return { ...saved, agents };
654
+ },
655
+ async claudeAgents(org) {
656
+ return (await claudeFor(org)).listAgents();
657
+ },
658
+ async removeClaudeKey(org, by) {
659
+ await stopSessions(org, () => true, CLAUDE_RUNNER);
660
+ const removed = await teams.removeClaudeKey(org);
661
+ if (removed)
662
+ await log(org, by, "remove_claude_key", "removed");
663
+ return removed;
664
+ },
665
+ };
666
+ }
667
+ /** During a key rotation, moves every bot credential, Claude key and trigger secret still sealed with the
668
+ * previous key onto the current one (only over what was read), so the previous key can be
669
+ * dropped. Connections and the Microsoft sign-in are re-sealed by their own checks. */
670
+ export async function resealTeamsCredentials(teams, key) {
671
+ if (!key.previousIds.length)
672
+ return { bots: 0, claudeKeys: 0, triggerSecrets: 0 };
673
+ const stale = await teams.staleSealed(key.previousIds);
674
+ let bots = 0;
675
+ for (const b of stale.bots) {
676
+ const aad = botAad(b.organizationId, b.id);
677
+ const secret = key.open({ ciphertext: b.credential, keyId: b.credentialKeyId }, aad);
678
+ const sealed = key.seal(secret, aad);
679
+ if (await teams.updateBotCredential(b.id, b.config, sealed.ciphertext, sealed.keyId, b.credential))
680
+ bots++;
681
+ }
682
+ let claudeKeys = 0;
683
+ for (const c of stale.claudeKeys) {
684
+ const aad = credentialAad(c.organizationId, CLAUDE_SCOPE);
685
+ const secret = key.open({ ciphertext: c.credential, keyId: c.credentialKeyId }, aad);
686
+ const sealed = key.seal(secret, aad);
687
+ if (await teams.resealClaudeKey(c.id, sealed.ciphertext, sealed.keyId, c.credential))
688
+ claudeKeys++;
689
+ }
690
+ let triggerSecrets = 0;
691
+ for (const t of stale.triggers) {
692
+ const aad = triggerAad(t.organizationId, t.agentId);
693
+ const sealed = key.seal(key.open({ ciphertext: t.secret, keyId: t.secretKeyId }, aad), aad);
694
+ if (await teams.resealTriggerSecret(t.agentId, sealed.ciphertext, sealed.keyId, t.secret))
695
+ triggerSecrets++;
696
+ }
697
+ return { bots, claudeKeys, triggerSecrets };
698
+ }
699
+ //# sourceMappingURL=admin.js.map