diff --git a/package.json b/package.json index ef1b0ca1e..fa1861538 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b89d4f599..5e4aa8faa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/config/types.ts b/src/config/types.ts index 1ccc17df6..372e936d4 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -87,6 +87,7 @@ export type AgentElevatedAllowFromConfig = { slack?: Array; signal?: Array; imessage?: Array; + msteams?: Array; webchat?: Array; }; @@ -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; } & 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; +}; + +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; + /** 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; + /** 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; +}; + 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; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 462a54cda..7396417c2 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -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(), }) diff --git a/src/cron/isolated-agent.ts b/src/cron/isolated-agent.ts index 1bac42113..6711850b7 100644 --- a/src/cron/isolated-agent.ts +++ b/src/cron/isolated-agent.ts @@ -160,7 +160,8 @@ function resolveDeliveryTarget( | "discord" | "slack" | "signal" - | "imessage"; + | "imessage" + | "msteams"; to?: string; }, ) { diff --git a/src/cron/types.ts b/src/cron/types.ts index 7a0f1009a..1112a4100 100644 --- a/src/cron/types.ts +++ b/src/cron/types.ts @@ -23,7 +23,8 @@ export type CronPayload = | "discord" | "slack" | "signal" - | "imessage"; + | "imessage" + | "msteams"; to?: string; bestEffortDeliver?: boolean; }; diff --git a/src/gateway/hooks-mapping.ts b/src/gateway/hooks-mapping.ts index 3216abadd..f71fd465d 100644 --- a/src/gateway/hooks-mapping.ts +++ b/src/gateway/hooks-mapping.ts @@ -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; diff --git a/src/gateway/server-http.ts b/src/gateway/server-http.ts index 5f6f1ddbf..4b81261cd 100644 --- a/src/gateway/server-http.ts +++ b/src/gateway/server-http.ts @@ -39,7 +39,8 @@ type HookDispatchers = { | "discord" | "slack" | "signal" - | "imessage"; + | "imessage" + | "msteams"; to?: string; model?: string; thinking?: string; diff --git a/src/gateway/server-providers.ts b/src/gateway/server-providers.ts index a447d89e4..c13016ecf 100644 --- a/src/gateway/server-providers.ts +++ b/src/gateway/server-providers.ts @@ -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; @@ -101,6 +109,7 @@ export type ProviderRuntimeSnapshot = { signalAccounts?: Record; imessage: IMessageRuntimeStatus; imessageAccounts?: Record; + msteams: MSTeamsRuntimeStatus; }; type SubsystemLogger = ReturnType; @@ -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; startIMessageProvider: (accountId?: string) => Promise; stopIMessageProvider: (accountId?: string) => Promise; + startMSTeamsProvider: () => Promise; + stopMSTeamsProvider: () => Promise; 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(); @@ -164,7 +179,9 @@ export function createProviderManager( const slackAborts = new Map(); const signalAborts = new Map(); const imessageAborts = new Map(); + let msteamsAbort: AbortController | null = null; const whatsappTasks = new Map>(); + let msteamsTask: Promise | null = null; const telegramTasks = new Map>(); const discordTasks = new Map>(); const slackTasks = new Map>(); @@ -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, }; } diff --git a/src/gateway/server.ts b/src/gateway/server.ts index cfd2a849d..3c244d1ba 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -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; diff --git a/src/msteams/index.ts b/src/msteams/index.ts new file mode 100644 index 000000000..b24578cc9 --- /dev/null +++ b/src/msteams/index.ts @@ -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"; diff --git a/src/msteams/monitor.ts b/src/msteams/monitor.ts new file mode 100644 index 000000000..429f5b733 --- /dev/null +++ b/src/msteams/monitor.ts @@ -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; +}; + +export async function monitorMSTeamsProvider( + opts: MonitorMSTeamsOpts, +): Promise { + 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; + + // Create activity handler using fluent API + const handler = new ActivityHandler() + .onMessage(async (context: TurnContext, next: () => Promise) => { + 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) => { + 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 }; +} diff --git a/src/msteams/probe.ts b/src/msteams/probe.ts new file mode 100644 index 000000000..ecb4ecae1 --- /dev/null +++ b/src/msteams/probe.ts @@ -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 { + 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 }; +} diff --git a/src/msteams/send.ts b/src/msteams/send.ts new file mode 100644 index 000000000..3e62c75f7 --- /dev/null +++ b/src/msteams/send.ts @@ -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 { + // TODO: Implement using CloudAdapter.continueConversationAsync + log.warn("sendMessageMSTeams not yet implemented"); + return { ok: false, error: "not implemented" }; +} diff --git a/src/msteams/token.ts b/src/msteams/token.ts new file mode 100644 index 000000000..01d03acde --- /dev/null +++ b/src/msteams/token.ts @@ -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 }; +} diff --git a/tmp/msteams-implementation-guide.md b/tmp/msteams-implementation-guide.md index 962904b59..af1464faf 100644 --- a/tmp/msteams-implementation-guide.md +++ b/tmp/msteams-implementation-guide.md @@ -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, Microsoft’s 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 +``` -CloudAdapter’s request processing explicitly requires parsed JSON bodies (it will 400 if `req.body` isn’t 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 you’re 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`.