feat(msteams): add MS Teams provider skeleton

- Add Microsoft 365 Agents SDK packages (@microsoft/agents-hosting,
  @microsoft/agents-hosting-express, @microsoft/agents-hosting-extensions-teams)
- Add MSTeamsConfig type and zod schema
- Create src/msteams/ provider with monitor, token, send, probe
- Wire provider into gateway (server-providers.ts, server.ts)
- Add msteams to all provider type unions (hooks, queue, cron, etc.)
- Update implementation guide with new SDK and progress
This commit is contained in:
Onur
2026-01-07 21:29:39 +03:00
committed by Peter Steinberger
parent 7274d6e757
commit d9cbecac7f
16 changed files with 708 additions and 41 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "clawdbot",
"version": "2026.1.9",
"version": "2026.1.8-2",
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
"type": "module",
"main": "dist/index.js",
@@ -101,6 +101,9 @@
"@mariozechner/pi-ai": "^0.41.0",
"@mariozechner/pi-coding-agent": "^0.41.0",
"@mariozechner/pi-tui": "^0.41.0",
"@microsoft/agents-hosting": "^1.1.1",
"@microsoft/agents-hosting-express": "^1.1.1",
"@microsoft/agents-hosting-extensions-teams": "^1.1.1",
"@sinclair/typebox": "0.34.47",
"@slack/bolt": "^4.6.0",
"@slack/web-api": "^7.13.0",

248
pnpm-lock.yaml generated
View File

@@ -43,6 +43,15 @@ importers:
'@mariozechner/pi-tui':
specifier: ^0.41.0
version: 0.41.0
'@microsoft/agents-hosting':
specifier: ^1.1.1
version: 1.1.1
'@microsoft/agents-hosting-express':
specifier: ^1.1.1
version: 1.1.1
'@microsoft/agents-hosting-extensions-teams':
specifier: ^1.1.1
version: 1.1.1
'@sinclair/typebox':
specifier: 0.34.47
version: 0.34.47
@@ -252,6 +261,26 @@ packages:
zod:
optional: true
'@azure/abort-controller@2.1.2':
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
engines: {node: '>=18.0.0'}
'@azure/core-auth@1.10.1':
resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==}
engines: {node: '>=20.0.0'}
'@azure/core-util@1.13.1':
resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==}
engines: {node: '>=20.0.0'}
'@azure/msal-common@15.13.3':
resolution: {integrity: sha512-shSDU7Ioecya+Aob5xliW9IGq1Ui8y4EVSdWGyI1Gbm4Vg61WpP95LuzcY214/wEjSn6w4PZYD4/iVldErHayQ==}
engines: {node: '>=0.8.0'}
'@azure/msal-node@3.8.4':
resolution: {integrity: sha512-lvuAwsDpPDE/jSuVQOBMpLbXuVuLsPNRwWCyK3/6bPlBk0fGWegqoZ0qjZclMWyQ2JNvIY3vHY7hoFmFmFQcOw==}
engines: {node: '>=16'}
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -830,6 +859,22 @@ packages:
resolution: {integrity: sha512-FxhNyQfsQvZJBbUIPbtvBzF8yJo2JjEXVksn5cUU8Qphw8z1Uf+bRXeleH7Q7VVvGnaH9zJR3r2cfkaWxC1Jig==}
engines: {node: '>=20.0.0'}
'@microsoft/agents-activity@1.1.1':
resolution: {integrity: sha512-L7PHEHKFge99aIxV9eA7uFY3n9goYKzxcWaqLXGmxq3wMsau8hdsPzZgpV77LOQWQynLO3M5cbD8AavcVZszlQ==}
engines: {node: '>=20.0.0'}
'@microsoft/agents-hosting-express@1.1.1':
resolution: {integrity: sha512-CDStIx23U2zyS/4nZoeVgrVlVbQ+EasoqR2dLq7IfU4rUyuUrKGPdlO55rcfS6Z/spLkhCnX35jbD6EBqrTkJg==}
engines: {node: '>=20.0.0'}
'@microsoft/agents-hosting-extensions-teams@1.1.1':
resolution: {integrity: sha512-ibwwEIJEKyx0VWMDPbvMRgbk97BXDij0qYIxsn1NNPrdzu6uY/33ZW0NF8eLKiJ/fVihIFGEFDeOwoE5R2bXZA==}
engines: {node: '>=20.0.0'}
'@microsoft/agents-hosting@1.1.1':
resolution: {integrity: sha512-ZO/BU0d/NxSlbg/W4SvtHDvwS4GDYrMG5CpBh+m2vnqkl6tphM0kkfbSYZFef0BoftrinOdPZcSvdvmVqpbM2w==}
engines: {node: '>=20.0.0'}
'@mistralai/mistralai@1.10.0':
resolution: {integrity: sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg==}
@@ -1250,9 +1295,15 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/express-serve-static-core@4.19.7':
resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==}
'@types/express-serve-static-core@5.1.0':
resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==}
'@types/express@4.17.25':
resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==}
'@types/express@5.0.6':
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
@@ -1277,6 +1328,9 @@ packages:
'@types/mime-types@2.1.4':
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
@@ -1310,9 +1364,15 @@ packages:
'@types/retry@0.12.5':
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
'@types/send@0.17.6':
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
'@types/send@1.2.1':
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
'@types/serve-static@1.15.10':
resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==}
'@types/serve-static@2.2.0':
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
@@ -1322,6 +1382,10 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@typespec/ts-http-runtime@0.3.2':
resolution: {integrity: sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==}
engines: {node: '>=20.0.0'}
'@vitest/browser-playwright@4.0.16':
resolution: {integrity: sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==}
peerDependencies:
@@ -2000,6 +2064,10 @@ packages:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -2087,6 +2155,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jose@4.15.9:
resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==}
js-base64@3.7.8:
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
@@ -2124,6 +2195,10 @@ packages:
jwa@2.0.1:
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
jwks-rsa@3.2.0:
resolution: {integrity: sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==}
engines: {node: '>=14'}
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
@@ -2207,6 +2282,9 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
limiter@1.1.5:
resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
@@ -2219,6 +2297,9 @@ packages:
lit@3.3.2:
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
@@ -2256,6 +2337,13 @@ packages:
resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
engines: {node: 20 || >=22}
lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
lru-memoizer@2.3.0:
resolution: {integrity: sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==}
lucide@0.544.0:
resolution: {integrity: sha512-U5ORwr5z9Sx7bNTDFaW55RbjVdQEnAcT3vws9uz3vRT1G4XXJUDAhRZdxhFoIyHEvjmTkzzlEhjSLYM5n4mb5w==}
@@ -2409,6 +2497,10 @@ packages:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
object-path@0.11.8:
resolution: {integrity: sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==}
engines: {node: '>= 10.12.0'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
@@ -2983,6 +3075,14 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@11.1.0:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -3131,6 +3231,9 @@ packages:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.8.2:
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
engines: {node: '>= 14.6'}
@@ -3149,6 +3252,9 @@ packages:
peerDependencies:
zod: ^3.25 || ^4
zod@3.25.75:
resolution: {integrity: sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==}
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
@@ -3163,6 +3269,34 @@ snapshots:
optionalDependencies:
zod: 4.3.5
'@azure/abort-controller@2.1.2':
dependencies:
tslib: 2.8.1
'@azure/core-auth@1.10.1':
dependencies:
'@azure/abort-controller': 2.1.2
'@azure/core-util': 1.13.1
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
'@azure/core-util@1.13.1':
dependencies:
'@azure/abort-controller': 2.1.2
'@typespec/ts-http-runtime': 0.3.2
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
'@azure/msal-common@15.13.3': {}
'@azure/msal-node@3.8.4':
dependencies:
'@azure/msal-common': 15.13.3
jsonwebtoken: 9.0.3
uuid: 8.3.2
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -3675,6 +3809,42 @@ snapshots:
marked: 15.0.12
mime-types: 3.0.2
'@microsoft/agents-activity@1.1.1':
dependencies:
debug: 4.4.3
uuid: 11.1.0
zod: 3.25.75
transitivePeerDependencies:
- supports-color
'@microsoft/agents-hosting-express@1.1.1':
dependencies:
'@microsoft/agents-hosting': 1.1.1
express: 5.2.1
transitivePeerDependencies:
- debug
- supports-color
'@microsoft/agents-hosting-extensions-teams@1.1.1':
dependencies:
'@microsoft/agents-hosting': 1.1.1
transitivePeerDependencies:
- debug
- supports-color
'@microsoft/agents-hosting@1.1.1':
dependencies:
'@azure/core-auth': 1.10.1
'@azure/msal-node': 3.8.4
'@microsoft/agents-activity': 1.1.1
axios: 1.13.2
jsonwebtoken: 9.0.3
jwks-rsa: 3.2.0
object-path: 0.11.8
transitivePeerDependencies:
- debug
- supports-color
'@mistralai/mistralai@1.10.0':
dependencies:
zod: 3.25.76
@@ -4029,6 +4199,13 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/express-serve-static-core@4.19.7':
dependencies:
'@types/node': 25.0.3
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
'@types/express-serve-static-core@5.1.0':
dependencies:
'@types/node': 25.0.3
@@ -4036,6 +4213,13 @@ snapshots:
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
'@types/express@4.17.25':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 4.19.7
'@types/qs': 6.14.0
'@types/serve-static': 1.15.10
'@types/express@5.0.6':
dependencies:
'@types/body-parser': 1.19.6
@@ -4062,6 +4246,8 @@ snapshots:
'@types/mime-types@2.1.4': {}
'@types/mime@1.3.5': {}
'@types/ms@2.1.0': {}
'@types/node@10.17.60': {}
@@ -4093,10 +4279,21 @@ snapshots:
'@types/retry@0.12.5': {}
'@types/send@0.17.6':
dependencies:
'@types/mime': 1.3.5
'@types/node': 25.0.3
'@types/send@1.2.1':
dependencies:
'@types/node': 25.0.3
'@types/serve-static@1.15.10':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 25.0.3
'@types/send': 0.17.6
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
@@ -4108,6 +4305,14 @@ snapshots:
dependencies:
'@types/node': 25.0.3
'@typespec/ts-http-runtime@0.3.2':
dependencies:
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
'@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)':
dependencies:
'@vitest/browser': 4.0.16(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)
@@ -4905,6 +5110,13 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
@@ -4983,6 +5195,8 @@ snapshots:
jiti@2.6.1: {}
jose@4.15.9: {}
js-base64@3.7.8: {}
js-tokens@4.0.0:
@@ -5031,6 +5245,17 @@ snapshots:
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jwks-rsa@3.2.0:
dependencies:
'@types/express': 4.17.25
'@types/jsonwebtoken': 9.0.10
debug: 4.4.3
jose: 4.15.9
limiter: 1.1.5
lru-memoizer: 2.3.0
transitivePeerDependencies:
- supports-color
jws@4.0.1:
dependencies:
jwa: 2.0.1
@@ -5098,6 +5323,8 @@ snapshots:
lightningcss-win32-x64-msvc: 1.30.2
optional: true
limiter@1.1.5: {}
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
@@ -5118,6 +5345,8 @@ snapshots:
lit-element: 4.2.2
lit-html: 3.3.2
lodash.clonedeep@4.5.0: {}
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}
@@ -5142,6 +5371,15 @@ snapshots:
lru-cache@11.2.4: {}
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
lru-memoizer@2.3.0:
dependencies:
lodash.clonedeep: 4.5.0
lru-cache: 6.0.0
lucide@0.544.0: {}
lucide@0.562.0: {}
@@ -5271,6 +5509,8 @@ snapshots:
object-inspect@1.13.4: {}
object-path@0.11.8: {}
obug@2.1.1: {}
ogg-opus-decoder@1.7.3:
@@ -5936,6 +6176,10 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@11.1.0: {}
uuid@8.3.2: {}
vary@1.1.2: {}
vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2):
@@ -6044,6 +6288,8 @@ snapshots:
y18n@5.0.8: {}
yallist@4.0.0: {}
yaml@2.8.2: {}
yargs-parser@20.2.9: {}
@@ -6066,6 +6312,8 @@ snapshots:
dependencies:
zod: 4.3.5
zod@3.25.75: {}
zod@3.25.76: {}
zod@4.3.5: {}

View File

@@ -87,6 +87,7 @@ export type AgentElevatedAllowFromConfig = {
slack?: Array<string | number>;
signal?: Array<string | number>;
imessage?: Array<string | number>;
msteams?: Array<string | number>;
webchat?: Array<string | number>;
};
@@ -214,7 +215,8 @@ export type HookMappingConfig = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
/** Override model for this hook (provider/model or alias). */
model?: string;
@@ -569,6 +571,64 @@ export type SignalConfig = {
accounts?: Record<string, SignalAccountConfig>;
} & SignalAccountConfig;
export type MSTeamsWebhookConfig = {
/** Port for the webhook server. Default: 3978. */
port?: number;
/** Path for the messages endpoint. Default: /api/messages. */
path?: string;
};
/** Reply style for MS Teams messages. */
export type MSTeamsReplyStyle = "thread" | "top-level";
/** Channel-level config for MS Teams. */
export type MSTeamsChannelConfig = {
/** Require @mention to respond. Default: true. */
requireMention?: boolean;
/** Reply style: "thread" replies to the message, "top-level" posts a new message. */
replyStyle?: MSTeamsReplyStyle;
};
/** Team-level config for MS Teams. */
export type MSTeamsTeamConfig = {
/** Default requireMention for channels in this team. */
requireMention?: boolean;
/** Default reply style for channels in this team. */
replyStyle?: MSTeamsReplyStyle;
/** Per-channel overrides. Key is conversation ID (e.g., "19:...@thread.tacv2"). */
channels?: Record<string, MSTeamsChannelConfig>;
};
export type MSTeamsConfig = {
/** If false, do not start the MS Teams provider. Default: true. */
enabled?: boolean;
/** Azure Bot App ID (from Azure Bot registration). */
appId?: string;
/** Azure Bot App Password / Client Secret. */
appPassword?: string;
/** Azure AD Tenant ID (for single-tenant bots). */
tenantId?: string;
/** Webhook server configuration. */
webhook?: MSTeamsWebhookConfig;
/** Direct message access policy (default: pairing). */
dmPolicy?: DmPolicy;
/** Allowlist for DM senders (AAD object IDs or UPNs). */
allowFrom?: Array<string>;
/** Outbound text chunk size (chars). Default: 4000. */
textChunkLimit?: number;
/**
* Allowed host suffixes for inbound attachment downloads.
* Use ["*"] to allow any host (not recommended).
*/
mediaAllowHosts?: Array<string>;
/** Default: require @mention to respond in channels/groups. */
requireMention?: boolean;
/** Default reply style: "thread" replies to the message, "top-level" posts a new message. */
replyStyle?: MSTeamsReplyStyle;
/** Per-team config. Key is team ID (from the /team/ URL path segment). */
teams?: Record<string, MSTeamsTeamConfig>;
};
export type IMessageAccountConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
@@ -631,6 +691,7 @@ export type QueueModeByProvider = {
slack?: QueueMode;
signal?: QueueMode;
imessage?: QueueMode;
msteams?: QueueMode;
webchat?: QueueMode;
};
@@ -875,13 +936,6 @@ export type GatewayTailscaleConfig = {
export type GatewayRemoteConfig = {
/** Remote Gateway WebSocket URL (ws:// or wss://). */
url?: string;
/**
* Remote gateway over SSH, forwarding the gateway port to localhost.
* Format: "user@host" or "user@host:port" (port defaults to 22).
*/
sshTarget?: string;
/** Optional SSH identity file path. */
sshIdentity?: string;
/** Token for remote auth (when the gateway requires token auth). */
token?: string;
/** Password for remote auth (when the gateway requires password auth). */
@@ -1126,7 +1180,7 @@ export type ClawdbotConfig = {
every?: string;
/** Heartbeat model override (provider/model). */
model?: string;
/** Delivery target (last|whatsapp|telegram|discord|signal|imessage|none). */
/** Delivery target (last|whatsapp|telegram|discord|signal|imessage|msteams|none). */
target?:
| "last"
| "whatsapp"
@@ -1135,6 +1189,7 @@ export type ClawdbotConfig = {
| "slack"
| "signal"
| "imessage"
| "msteams"
| "none";
/** Optional delivery override (E.164 for WhatsApp, chat id for Telegram). */
to?: string;
@@ -1225,6 +1280,7 @@ export type ClawdbotConfig = {
slack?: SlackConfig;
signal?: SignalConfig;
imessage?: IMessageConfig;
msteams?: MSTeamsConfig;
cron?: CronConfig;
hooks?: HooksConfig;
bridge?: BridgeConfig;

View File

@@ -109,6 +109,8 @@ const requireOpenAllowFrom = (params: {
});
};
const MSTeamsReplyStyleSchema = z.enum(["thread", "top-level"]);
const RetryConfigSchema = z
.object({
attempts: z.number().int().min(1).optional(),
@@ -126,6 +128,7 @@ const QueueModeBySurfaceSchema = z
slack: QueueModeSchema.optional(),
signal: QueueModeSchema.optional(),
imessage: QueueModeSchema.optional(),
msteams: QueueModeSchema.optional(),
webchat: QueueModeSchema.optional(),
})
.optional();
@@ -455,6 +458,48 @@ const IMessageConfigSchema = IMessageAccountSchemaBase.extend({
});
});
const MSTeamsChannelSchema = z.object({
requireMention: z.boolean().optional(),
replyStyle: MSTeamsReplyStyleSchema.optional(),
});
const MSTeamsTeamSchema = z.object({
requireMention: z.boolean().optional(),
replyStyle: MSTeamsReplyStyleSchema.optional(),
channels: z.record(z.string(), MSTeamsChannelSchema.optional()).optional(),
});
const MSTeamsConfigSchema = z
.object({
enabled: z.boolean().optional(),
appId: z.string().optional(),
appPassword: z.string().optional(),
tenantId: z.string().optional(),
webhook: z
.object({
port: z.number().int().positive().optional(),
path: z.string().optional(),
})
.optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
allowFrom: z.array(z.string()).optional(),
textChunkLimit: z.number().int().positive().optional(),
mediaAllowHosts: z.array(z.string()).optional(),
requireMention: z.boolean().optional(),
replyStyle: MSTeamsReplyStyleSchema.optional(),
teams: z.record(z.string(), MSTeamsTeamSchema.optional()).optional(),
})
.superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message:
'msteams.dmPolicy="open" requires msteams.allowFrom to include "*"',
});
});
const SessionSchema = z
.object({
scope: z.union([z.literal("per-sender"), z.literal("global")]).optional(),
@@ -742,6 +787,7 @@ const HookMappingSchema = z
z.literal("slack"),
z.literal("signal"),
z.literal("imessage"),
z.literal("msteams"),
])
.optional(),
to: z.string().optional(),
@@ -1049,6 +1095,7 @@ export const ClawdbotSchema = z.object({
slack: z.array(z.union([z.string(), z.number()])).optional(),
signal: z.array(z.union([z.string(), z.number()])).optional(),
imessage: z.array(z.union([z.string(), z.number()])).optional(),
msteams: z.array(z.union([z.string(), z.number()])).optional(),
webchat: z.array(z.union([z.string(), z.number()])).optional(),
})
.optional(),
@@ -1205,6 +1252,7 @@ export const ClawdbotSchema = z.object({
slack: SlackConfigSchema.optional(),
signal: SignalConfigSchema.optional(),
imessage: IMessageConfigSchema.optional(),
msteams: MSTeamsConfigSchema.optional(),
bridge: z
.object({
enabled: z.boolean().optional(),
@@ -1283,8 +1331,6 @@ export const ClawdbotSchema = z.object({
remote: z
.object({
url: z.string().optional(),
sshTarget: z.string().optional(),
sshIdentity: z.string().optional(),
token: z.string().optional(),
password: z.string().optional(),
})

View File

@@ -160,7 +160,8 @@ function resolveDeliveryTarget(
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
},
) {

View File

@@ -23,7 +23,8 @@ export type CronPayload =
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
bestEffortDeliver?: boolean;
};

View File

@@ -25,7 +25,8 @@ export type HookMappingResolved = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;
@@ -65,7 +66,8 @@ export type HookAction =
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;

View File

@@ -39,7 +39,8 @@ type HookDispatchers = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;

View File

@@ -88,6 +88,14 @@ export type IMessageRuntimeStatus = {
dbPath?: string | null;
};
export type MSTeamsRuntimeStatus = {
running: boolean;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastError?: string | null;
port?: number | null;
};
export type ProviderRuntimeSnapshot = {
whatsapp: WebProviderStatus;
whatsappAccounts?: Record<string, WebProviderStatus>;
@@ -101,6 +109,7 @@ export type ProviderRuntimeSnapshot = {
signalAccounts?: Record<string, SignalRuntimeStatus>;
imessage: IMessageRuntimeStatus;
imessageAccounts?: Record<string, IMessageRuntimeStatus>;
msteams: MSTeamsRuntimeStatus;
};
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
@@ -113,12 +122,14 @@ type ProviderManagerOptions = {
logSlack: SubsystemLogger;
logSignal: SubsystemLogger;
logIMessage: SubsystemLogger;
logMSTeams: SubsystemLogger;
whatsappRuntimeEnv: RuntimeEnv;
telegramRuntimeEnv: RuntimeEnv;
discordRuntimeEnv: RuntimeEnv;
slackRuntimeEnv: RuntimeEnv;
signalRuntimeEnv: RuntimeEnv;
imessageRuntimeEnv: RuntimeEnv;
msteamsRuntimeEnv: RuntimeEnv;
};
export type ProviderManager = {
@@ -136,6 +147,8 @@ export type ProviderManager = {
stopSignalProvider: (accountId?: string) => Promise<void>;
startIMessageProvider: (accountId?: string) => Promise<void>;
stopIMessageProvider: (accountId?: string) => Promise<void>;
startMSTeamsProvider: () => Promise<void>;
stopMSTeamsProvider: () => Promise<void>;
markWhatsAppLoggedOut: (cleared: boolean, accountId?: string) => void;
};
@@ -150,12 +163,14 @@ export function createProviderManager(
logSlack,
logSignal,
logIMessage,
logMSTeams,
whatsappRuntimeEnv,
telegramRuntimeEnv,
discordRuntimeEnv,
slackRuntimeEnv,
signalRuntimeEnv,
imessageRuntimeEnv,
msteamsRuntimeEnv,
} = opts;
const whatsappAborts = new Map<string, AbortController>();
@@ -164,7 +179,9 @@ export function createProviderManager(
const slackAborts = new Map<string, AbortController>();
const signalAborts = new Map<string, AbortController>();
const imessageAborts = new Map<string, AbortController>();
let msteamsAbort: AbortController | null = null;
const whatsappTasks = new Map<string, Promise<unknown>>();
let msteamsTask: Promise<unknown> | null = null;
const telegramTasks = new Map<string, Promise<unknown>>();
const discordTasks = new Map<string, Promise<unknown>>();
const slackTasks = new Map<string, Promise<unknown>>();
@@ -224,6 +241,13 @@ export function createProviderManager(
cliPath: null,
dbPath: null,
});
let msteamsRuntime: MSTeamsRuntimeStatus = {
running: false,
lastStartAt: null,
lastStopAt: null,
lastError: null,
port: null,
};
const updateWhatsAppStatus = (accountId: string, next: WebProviderStatus) => {
whatsappRuntimes.set(accountId, next);
@@ -1026,6 +1050,83 @@ export function createProviderManager(
);
};
const startMSTeamsProvider = async () => {
if (msteamsTask) return;
const cfg = loadConfig();
if (!cfg.msteams) {
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastError: "not configured",
};
if (shouldLogVerbose()) {
logMSTeams.debug("msteams provider not configured (no msteams config)");
}
return;
}
if (cfg.msteams?.enabled === false) {
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastError: "disabled",
};
if (shouldLogVerbose()) {
logMSTeams.debug("msteams provider disabled (msteams.enabled=false)");
}
return;
}
const { monitorMSTeamsProvider } = await import("../msteams/index.js");
const port = cfg.msteams?.webhook?.port ?? 3978;
logMSTeams.info(`starting provider (port ${port})`);
msteamsAbort = new AbortController();
msteamsRuntime = {
...msteamsRuntime,
running: true,
lastStartAt: Date.now(),
lastError: null,
port,
};
const task = monitorMSTeamsProvider({
cfg,
runtime: msteamsRuntimeEnv,
abortSignal: msteamsAbort.signal,
})
.catch((err) => {
msteamsRuntime = {
...msteamsRuntime,
lastError: formatError(err),
};
logMSTeams.error(`provider exited: ${formatError(err)}`);
})
.finally(() => {
msteamsAbort = null;
msteamsTask = null;
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastStopAt: Date.now(),
};
});
msteamsTask = task;
};
const stopMSTeamsProvider = async () => {
if (!msteamsAbort && !msteamsTask) return;
msteamsAbort?.abort();
try {
await msteamsTask;
} catch {
// ignore
}
msteamsAbort = null;
msteamsTask = null;
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastStopAt: Date.now(),
};
};
const startProviders = async () => {
await startWhatsAppProvider();
await startDiscordProvider();
@@ -1033,6 +1134,7 @@ export function createProviderManager(
await startTelegramProvider();
await startSignalProvider();
await startIMessageProvider();
await startMSTeamsProvider();
};
const markWhatsAppLoggedOut = (cleared: boolean, accountId?: string) => {
@@ -1180,6 +1282,7 @@ export function createProviderManager(
signalAccounts,
imessage,
imessageAccounts,
msteams: { ...msteamsRuntime },
};
};
@@ -1198,6 +1301,8 @@ export function createProviderManager(
stopSignalProvider,
startIMessageProvider,
stopIMessageProvider,
startMSTeamsProvider,
stopMSTeamsProvider,
markWhatsAppLoggedOut,
};
}

View File

@@ -183,6 +183,7 @@ const logDiscord = logProviders.child("discord");
const logSlack = logProviders.child("slack");
const logSignal = logProviders.child("signal");
const logIMessage = logProviders.child("imessage");
const logMSTeams = logProviders.child("msteams");
const canvasRuntime = runtimeForLogger(logCanvas);
const whatsappRuntimeEnv = runtimeForLogger(logWhatsApp);
const telegramRuntimeEnv = runtimeForLogger(logTelegram);
@@ -190,6 +191,7 @@ const discordRuntimeEnv = runtimeForLogger(logDiscord);
const slackRuntimeEnv = runtimeForLogger(logSlack);
const signalRuntimeEnv = runtimeForLogger(logSignal);
const imessageRuntimeEnv = runtimeForLogger(logIMessage);
const msteamsRuntimeEnv = runtimeForLogger(logMSTeams);
type GatewayModelChoice = ModelCatalogEntry;
@@ -501,7 +503,8 @@ export async function startGatewayServer(
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;
@@ -756,12 +759,14 @@ export async function startGatewayServer(
logSlack,
logSignal,
logIMessage,
logMSTeams,
whatsappRuntimeEnv,
telegramRuntimeEnv,
discordRuntimeEnv,
slackRuntimeEnv,
signalRuntimeEnv,
imessageRuntimeEnv,
msteamsRuntimeEnv,
});
const {
getRuntimeSnapshot,
@@ -772,12 +777,14 @@ export async function startGatewayServer(
startSlackProvider,
startSignalProvider,
startIMessageProvider,
startMSTeamsProvider,
stopWhatsAppProvider,
stopTelegramProvider,
stopDiscordProvider,
stopSlackProvider,
stopSignalProvider,
stopIMessageProvider,
stopMSTeamsProvider,
markWhatsAppLoggedOut,
} = providerManager;

4
src/msteams/index.ts Normal file
View File

@@ -0,0 +1,4 @@
export { monitorMSTeamsProvider } from "./monitor.js";
export { probeMSTeams } from "./probe.js";
export { sendMessageMSTeams } from "./send.js";
export { type MSTeamsCredentials, resolveMSTeamsCredentials } from "./token.js";

111
src/msteams/monitor.ts Normal file
View File

@@ -0,0 +1,111 @@
import type { ClawdbotConfig } from "../config/types.js";
import { getChildLogger } from "../logging.js";
import type { RuntimeEnv } from "../runtime.js";
import { resolveMSTeamsCredentials } from "./token.js";
const log = getChildLogger({ name: "msteams:monitor" });
export type MonitorMSTeamsOpts = {
cfg: ClawdbotConfig;
runtime?: RuntimeEnv;
abortSignal?: AbortSignal;
};
export type MonitorMSTeamsResult = {
app: unknown;
shutdown: () => Promise<void>;
};
export async function monitorMSTeamsProvider(
opts: MonitorMSTeamsOpts,
): Promise<MonitorMSTeamsResult> {
const msteamsCfg = opts.cfg.msteams;
if (!msteamsCfg?.enabled) {
log.debug("msteams provider disabled");
return { app: null, shutdown: async () => {} };
}
const creds = resolveMSTeamsCredentials(msteamsCfg);
if (!creds) {
log.error("msteams credentials not configured");
return { app: null, shutdown: async () => {} };
}
const port = msteamsCfg.webhook?.port ?? 3978;
const path = msteamsCfg.webhook?.path ?? "/msteams/messages";
log.info(`starting msteams provider on port ${port}${path}`);
// Dynamic import to avoid loading SDK when provider is disabled
const agentsHosting = await import("@microsoft/agents-hosting");
const { startServer } = await import("@microsoft/agents-hosting-express");
const { ActivityHandler } = agentsHosting;
type TurnContext = InstanceType<typeof agentsHosting.TurnContext>;
// Create activity handler using fluent API
const handler = new ActivityHandler()
.onMessage(async (context: TurnContext, next: () => Promise<void>) => {
const text = context.activity?.text?.trim() ?? "";
const from = context.activity?.from;
const conversation = context.activity?.conversation;
log.debug("received message", {
text: text.slice(0, 100),
from: from?.id,
conversation: conversation?.id,
});
// TODO: Implement full message handling
// - Route to agent based on config
// - Process commands
// - Send reply via context.sendActivity()
// Echo for now as a test
await context.sendActivity(`Received: ${text}`);
await next();
})
.onMembersAdded(async (context: TurnContext, next: () => Promise<void>) => {
const membersAdded = context.activity?.membersAdded ?? [];
for (const member of membersAdded) {
if (member.id !== context.activity?.recipient?.id) {
log.debug("member added", { member: member.id });
await context.sendActivity("Hello! I'm Clawdbot.");
}
}
await next();
});
// Auth configuration using the new SDK format
const authConfig = {
clientId: creds.appId,
clientSecret: creds.appPassword,
tenantId: creds.tenantId,
};
// Set env vars that startServer reads (it uses loadAuthConfigFromEnv internally)
process.env.clientId = creds.appId;
process.env.clientSecret = creds.appPassword;
process.env.tenantId = creds.tenantId;
process.env.PORT = String(port);
// Start the server
const expressApp = startServer(handler, authConfig);
log.info(`msteams provider started on port ${port}`);
const shutdown = async () => {
log.info("shutting down msteams provider");
// Express app doesn't have a direct close method
// The server is managed by startServer internally
};
// Handle abort signal
if (opts.abortSignal) {
opts.abortSignal.addEventListener("abort", () => {
void shutdown();
});
}
return { app: expressApp, shutdown };
}

23
src/msteams/probe.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { MSTeamsConfig } from "../config/types.js";
import { resolveMSTeamsCredentials } from "./token.js";
export type ProbeMSTeamsResult = {
ok: boolean;
error?: string;
appId?: string;
};
export async function probeMSTeams(
cfg?: MSTeamsConfig,
): Promise<ProbeMSTeamsResult> {
const creds = resolveMSTeamsCredentials(cfg);
if (!creds) {
return {
ok: false,
error: "missing credentials (appId, appPassword, tenantId)",
};
}
// TODO: Validate credentials by attempting to get a token
return { ok: true, appId: creds.appId };
}

25
src/msteams/send.ts Normal file
View File

@@ -0,0 +1,25 @@
import type { MSTeamsConfig } from "../config/types.js";
import { getChildLogger } from "../logging.js";
const log = getChildLogger({ name: "msteams:send" });
export type SendMSTeamsMessageParams = {
cfg: MSTeamsConfig;
conversationId: string;
text: string;
serviceUrl: string;
};
export type SendMSTeamsMessageResult = {
ok: boolean;
messageId?: string;
error?: string;
};
export async function sendMessageMSTeams(
_params: SendMSTeamsMessageParams,
): Promise<SendMSTeamsMessageResult> {
// TODO: Implement using CloudAdapter.continueConversationAsync
log.warn("sendMessageMSTeams not yet implemented");
return { ok: false, error: "not implemented" };
}

23
src/msteams/token.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { MSTeamsConfig } from "../config/types.js";
export type MSTeamsCredentials = {
appId: string;
appPassword: string;
tenantId: string;
};
export function resolveMSTeamsCredentials(
cfg?: MSTeamsConfig,
): MSTeamsCredentials | undefined {
const appId = cfg?.appId?.trim() || process.env.MSTEAMS_APP_ID?.trim();
const appPassword =
cfg?.appPassword?.trim() || process.env.MSTEAMS_APP_PASSWORD?.trim();
const tenantId =
cfg?.tenantId?.trim() || process.env.MSTEAMS_TENANT_ID?.trim();
if (!appId || !appPassword || !tenantId) {
return undefined;
}
return { appId, appPassword, tenantId };
}

View File

@@ -102,14 +102,23 @@ If we add `msteams`, the UI must be updated alongside backend config/types.
## 2) 2025/2026 Microsoft Guidance (What Changed)
### 2.1 Bot Framework SDK v4 “modern” baseline (Node)
### 2.1 Microsoft 365 Agents SDK (Recommended)
For Node bots, Microsofts maintained samples now use:
**UPDATE (2026-01):** The Bot Framework SDK (`botbuilder`) was deprecated in December 2025. We now use the **Microsoft 365 Agents SDK** which is the official replacement:
- `CloudAdapter` + `ConfigurationBotFrameworkAuthentication` (instead of older adapter patterns)
- Express/Restify middleware to parse JSON into `req.body` before `adapter.process(...)`
```bash
pnpm add @microsoft/agents-hosting @microsoft/agents-hosting-express @microsoft/agents-hosting-extensions-teams
```
CloudAdapters request processing explicitly requires parsed JSON bodies (it will 400 if `req.body` isnt an object).
The new SDK uses:
- `ActivityHandler` with fluent API for handling activities
- `startServer()` from `@microsoft/agents-hosting-express` for Express integration
- `AuthConfiguration` with `clientId`, `clientSecret`, `tenantId` (new naming)
Package sizes (for reference):
- `@microsoft/agents-hosting`: ~1.4 MB
- `@microsoft/agents-hosting-express`: ~12 KB
- `@microsoft/agents-hosting-extensions-teams`: ~537 KB (optional, for Teams-specific features)
### 2.2 Proactive messaging is required for “slow” work
@@ -125,14 +134,11 @@ Best practice for long-running work is:
- **return quickly**,
- then send replies later via proactive messaging (`continueConversationAsync` in CloudAdapter).
### 2.3 Microsoft 365 Agents SDK exists (potential future path)
### 2.3 SDK Migration Complete
Microsoft is actively building the **Microsoft 365 Agents SDK** (Node/TS) which positions itself as a replacement for parts of Bot Framework (`botbuilder`) for Teams and other channels.
We are using the **Microsoft 365 Agents SDK** (`@microsoft/agents-hosting` v1.1.1+) as the primary SDK. The deprecated Bot Framework SDK (`botbuilder`) is NOT used.
Practical implication for Clawdbot:
- **Ship v1 with Bot Framework** (most stable, most docs, matches Teams docs),
- but structure our MS Teams provider so it can be swapped to Agents SDK later (thin adapter boundary around “receive activity” + “send activity”).
GitHub: https://github.com/Microsoft/Agents-for-js
### 2.4 Deprecations / platform shifts to note
@@ -784,15 +790,20 @@ Initial recommendation: support this type first; treat other attachment types as
## Next Steps (Actionable Implementation Order)
1. **Pick SDK + add deps**: start with Bot Framework (`botbuilder`) unless youre ready to bet on Agents SDK; add packages + types in `package.json`.
2. **Config plumbing**: add `msteams` types + zod schema + schema metadata (`src/config/types.ts`, `src/config/zod-schema.ts`, `src/config/schema.ts`).
3. **Provider skeleton**: add `src/msteams/index.ts`, `token.ts`, and a stub `monitor.ts` that starts/stops cleanly (abortSignal).
4. **Webhook + echo**: implement `webhook.ts` + minimal activity handler that logs inbound text and sends a fast “ok” reply (no agent yet).
5. **Conversation store**: persist `ConversationReference` by `conversation.id` and include tenant/serviceUrl; add a small unit test.
6. **Agent dispatch (async)**: wire inbound messages to `dispatchReplyFromConfig()` using proactive sends (`continueConversationAsync`) to avoid webhook timeouts.
7. **Access control**: implement DM policy + pairing (reuse existing pairing store) + mention gating in channels.
8. **Gateway integration**: add provider manager start/stop/status wiring + config reload rules + hook provider allowlist; ensure gateway status UI reflects it.
9. **Outbound CLI/gateway sends**: add `sendMessageMSTeams` that targets stored conversation IDs; wire `clawdbot send --provider msteams`.
10. **Media**: implement inbound attachment download for `file.download.info` and a safe outbound strategy (link-only first, cards later).
11. **Docs + UI + Onboard**: write `docs/providers/msteams.md`, add a minimal UI config form (appId/secret/tenant + webhook port/path), and update `clawdbot onboard` provider selection.
12. **Hardening**: add dedupe TTL tuning, better error reporting, probe/health endpoints, and integration tests (`monitor.tool-result.test.ts`).
### Completed (2026-01-07)
1. **Add SDK packages**: Microsoft 365 Agents SDK (`@microsoft/agents-hosting`, `@microsoft/agents-hosting-express`, `@microsoft/agents-hosting-extensions-teams`)
2. **Config plumbing**: `MSTeamsConfig` type + zod schema (`src/config/types.ts`, `src/config/zod-schema.ts`)
3. **Provider skeleton**: `src/msteams/` with `index.ts`, `token.ts`, `probe.ts`, `send.ts`, `monitor.ts`
4. **Gateway integration**: Provider manager start/stop wiring in `server-providers.ts` and `server.ts`
### Remaining
5. **Test echo bot**: Run gateway with msteams enabled, verify Teams can reach the webhook and receive echo replies.
6. **Conversation store**: Persist `ConversationReference` by `conversation.id` for proactive messaging.
7. **Agent dispatch (async)**: Wire inbound messages to `dispatchReplyFromConfig()` using proactive sends.
8. **Access control**: Implement DM policy + pairing (reuse existing pairing store) + mention gating in channels.
9. **Config reload**: Add msteams to `config-reload.ts` restart rules.
10. **Outbound CLI/gateway sends**: Implement `sendMessageMSTeams` properly; wire `clawdbot send --provider msteams`.
11. **Media**: Implement inbound attachment download and outbound strategy.
12. **Docs + UI + Onboard**: Write `docs/providers/msteams.md`, add UI config form, update `clawdbot onboard`.