feat(googlechat): add Google Chat provider
Add Google Chat as the 8th messaging platform, using Pub/Sub for inbound messages and the Google Chat API for outbound. This enables messaging with Clawbot via Google Workspace's Google Chat. Features: - Pub/Sub-based message monitoring (firewall-friendly) - Direct message and space support - Thread support - Media via cards - DM/space policy configuration - Multi-account support Requires: - Google Workspace account (not @gmail.com) - Google Cloud project with Chat API and Pub/Sub enabled - Service account credentials Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
74526645eb
commit
8bf4401bd8
@ -127,6 +127,7 @@
|
||||
"dependencies": {
|
||||
"@buape/carbon": "0.0.0-beta-20260110172854",
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@google-cloud/pubsub": "^5.2.1",
|
||||
"@grammyjs/runner": "^2.0.3",
|
||||
"@grammyjs/transformer-throttler": "^1.2.1",
|
||||
"@homebridge/ciao": "^1.3.4",
|
||||
@ -153,6 +154,7 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"file-type": "^21.3.0",
|
||||
"googleapis": "^170.0.0",
|
||||
"grammy": "^1.39.2",
|
||||
"jiti": "^2.6.1",
|
||||
"json5": "^2.2.3",
|
||||
|
||||
360
pnpm-lock.yaml
generated
360
pnpm-lock.yaml
generated
@ -22,6 +22,9 @@ importers:
|
||||
'@clack/prompts':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0
|
||||
'@google-cloud/pubsub':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
'@grammyjs/runner':
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3(grammy@1.39.2)
|
||||
@ -100,6 +103,9 @@ importers:
|
||||
file-type:
|
||||
specifier: ^21.3.0
|
||||
version: 21.3.0
|
||||
googleapis:
|
||||
specifier: ^170.0.0
|
||||
version: 170.0.0
|
||||
grammy:
|
||||
specifier: ^1.39.2
|
||||
version: 1.39.2
|
||||
@ -220,7 +226,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^4.0.16
|
||||
version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
wireit:
|
||||
specifier: ^0.14.12
|
||||
version: 0.14.12
|
||||
@ -251,7 +257,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 4.0.16
|
||||
version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
packages:
|
||||
|
||||
@ -566,6 +572,26 @@ packages:
|
||||
'@glideapps/ts-necessities@2.2.3':
|
||||
resolution: {integrity: sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==}
|
||||
|
||||
'@google-cloud/paginator@6.0.0':
|
||||
resolution: {integrity: sha512-g5nmMnzC+94kBxOKkLGpK1ikvolTFCC3s2qtE4F+1EuArcJ7HHC23RDQVt3Ra3CqpUYZ+oXNKZ8n5Cn5yug8DA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@google-cloud/precise-date@5.0.0':
|
||||
resolution: {integrity: sha512-9h0Gvw92EvPdE8AK8AgZPbMnH5ftDyPtKm7/KUfcJVaPEPjwGDsJd1QV0H8esBDV4II41R/2lDWH1epBqIoKUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@google-cloud/projectify@5.0.0':
|
||||
resolution: {integrity: sha512-XXQLaIcLrOAMWvRrzz+mlUGtN6vlVNja3XQbMqRi/V7XJTAVwib3VcKd7oRwyZPkp7rBVlHGcaqdyGRrcnkhlA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@google-cloud/promisify@5.0.0':
|
||||
resolution: {integrity: sha512-N8qS6dlORGHwk7WjGXKOSsLjIjNINCPicsOX6gyyLiYk7mq3MtII96NZ9N2ahwA2vnkLmZODOIH9rlNniYWvCQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@google-cloud/pubsub@5.2.1':
|
||||
resolution: {integrity: sha512-AGuH/dN8059pLg1A4vMLFQfZEuIwLy1Ykmh8b4XmBM6eT93mAgmrV2y2JMq6dRWTIaBpYGFA7NjRviNL/W/fmQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@google/genai@1.34.0':
|
||||
resolution: {integrity: sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@ -590,6 +616,15 @@ packages:
|
||||
'@grammyjs/types@3.23.0':
|
||||
resolution: {integrity: sha512-D3jQ4UWERPsyR3op/YFudMMIPNTU47vy7L51uO9/73tMELmjO/+LX5N36/Y0CG5IQfIsz43MxiHI5rgsK0/k+g==}
|
||||
|
||||
'@grpc/grpc-js@1.14.3':
|
||||
resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==}
|
||||
engines: {node: '>=12.10.0'}
|
||||
|
||||
'@grpc/proto-loader@0.8.0':
|
||||
resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
'@hapi/boom@9.1.4':
|
||||
resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==}
|
||||
|
||||
@ -765,6 +800,9 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@js-sdsl/ordered-map@4.4.2':
|
||||
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
||||
|
||||
'@keyv/bigmap@1.3.0':
|
||||
resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==}
|
||||
engines: {node: '>= 18'}
|
||||
@ -900,6 +938,24 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@opentelemetry/core@1.30.1':
|
||||
resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.28.0':
|
||||
resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.34.0':
|
||||
resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@oxc-project/types@0.107.0':
|
||||
resolution: {integrity: sha512-QFDRbYfV2LVx8tyqtyiah3jQPUj1mK2+RYwxyFWyGoys6XJnwTdlzO6rdNNHOPorHAu5Uo34oWRKcvNpbJarmQ==}
|
||||
|
||||
@ -1278,6 +1334,10 @@ packages:
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
'@tootallnate/once@2.0.0':
|
||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
@ -1487,6 +1547,10 @@ packages:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@ -1538,6 +1602,10 @@ packages:
|
||||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
arrify@2.0.1:
|
||||
resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -1669,6 +1737,10 @@ packages:
|
||||
cliui@7.0.4:
|
||||
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
@ -1790,6 +1862,9 @@ packages:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
duplexify@4.1.3:
|
||||
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@ -1809,6 +1884,9 @@ packages:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
@ -2000,10 +2078,22 @@ packages:
|
||||
resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-gax@5.0.6:
|
||||
resolution: {integrity: sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-logging-utils@1.1.3:
|
||||
resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
resolution: {integrity: sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
googleapis@170.0.0:
|
||||
resolution: {integrity: sha512-UJz71WZ3ubMr4NhkEU+CFTS0CMrrXq+ltrFnAQo8Llf9M3cy0AIfKLyFQdUJyhqIpJ4jPW4SRCcBBntrLQ72/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -2039,6 +2129,10 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
heap-js@2.7.1:
|
||||
resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
highlight.js@10.7.3:
|
||||
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
||||
|
||||
@ -2063,10 +2157,18 @@ packages:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
@ -2114,6 +2216,9 @@ packages:
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
is-stream-ended@0.1.4:
|
||||
resolution: {integrity: sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==}
|
||||
|
||||
is-stream@2.0.1:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2296,6 +2401,9 @@ packages:
|
||||
lit@3.3.2:
|
||||
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
|
||||
|
||||
lodash.camelcase@4.3.0:
|
||||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
|
||||
lodash.clonedeep@4.5.0:
|
||||
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
||||
|
||||
@ -2320,6 +2428,9 @@ packages:
|
||||
lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
|
||||
lodash.snakecase@4.1.1:
|
||||
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@ -2492,6 +2603,10 @@ packages:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-hash@3.0.0:
|
||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -2553,6 +2668,10 @@ packages:
|
||||
oxlint-tsgolint:
|
||||
optional: true
|
||||
|
||||
p-defer@3.0.0:
|
||||
resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-finally@1.0.0:
|
||||
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
|
||||
engines: {node: '>=4'}
|
||||
@ -2701,6 +2820,10 @@ packages:
|
||||
proper-lockfile@4.1.2:
|
||||
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
|
||||
|
||||
proto3-json-serializer@3.0.4:
|
||||
resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
protobufjs@6.8.8:
|
||||
resolution: {integrity: sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==}
|
||||
hasBin: true
|
||||
@ -2758,6 +2881,10 @@ packages:
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
readable-stream@4.5.2:
|
||||
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@ -2785,6 +2912,10 @@ packages:
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
retry-request@8.0.2:
|
||||
resolution: {integrity: sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
retry@0.12.0:
|
||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||
engines: {node: '>= 4'}
|
||||
@ -2934,6 +3065,12 @@ packages:
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
stream-events@1.0.5:
|
||||
resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==}
|
||||
|
||||
stream-shift@1.0.3:
|
||||
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2960,6 +3097,9 @@ packages:
|
||||
resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
stubs@3.0.0:
|
||||
resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2980,6 +3120,10 @@ packages:
|
||||
tailwindcss@4.1.17:
|
||||
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
|
||||
|
||||
teeny-request@10.1.0:
|
||||
resolution: {integrity: sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
@ -3085,6 +3229,9 @@ packages:
|
||||
urijs@1.19.11:
|
||||
resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==}
|
||||
|
||||
url-template@2.0.8:
|
||||
resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
@ -3256,10 +3403,18 @@ packages:
|
||||
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@16.2.0:
|
||||
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
@ -3538,6 +3693,36 @@ snapshots:
|
||||
|
||||
'@glideapps/ts-necessities@2.2.3': {}
|
||||
|
||||
'@google-cloud/paginator@6.0.0':
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
|
||||
'@google-cloud/precise-date@5.0.0': {}
|
||||
|
||||
'@google-cloud/projectify@5.0.0': {}
|
||||
|
||||
'@google-cloud/promisify@5.0.0': {}
|
||||
|
||||
'@google-cloud/pubsub@5.2.1':
|
||||
dependencies:
|
||||
'@google-cloud/paginator': 6.0.0
|
||||
'@google-cloud/precise-date': 5.0.0
|
||||
'@google-cloud/projectify': 5.0.0
|
||||
'@google-cloud/promisify': 5.0.0
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.34.0
|
||||
arrify: 2.0.1
|
||||
extend: 3.0.2
|
||||
google-auth-library: 10.5.0
|
||||
google-gax: 5.0.6
|
||||
heap-js: 2.7.1
|
||||
is-stream-ended: 0.1.4
|
||||
lodash.snakecase: 4.1.1
|
||||
p-defer: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@google/genai@1.34.0':
|
||||
dependencies:
|
||||
google-auth-library: 10.5.0
|
||||
@ -3559,6 +3744,18 @@ snapshots:
|
||||
|
||||
'@grammyjs/types@3.23.0': {}
|
||||
|
||||
'@grpc/grpc-js@1.14.3':
|
||||
dependencies:
|
||||
'@grpc/proto-loader': 0.8.0
|
||||
'@js-sdsl/ordered-map': 4.4.2
|
||||
|
||||
'@grpc/proto-loader@0.8.0':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.3.2
|
||||
protobufjs: 7.5.4
|
||||
yargs: 17.7.2
|
||||
|
||||
'@hapi/boom@9.1.4':
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.3.0
|
||||
@ -3699,6 +3896,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@js-sdsl/ordered-map@4.4.2': {}
|
||||
|
||||
'@keyv/bigmap@1.3.0(keyv@5.5.5)':
|
||||
dependencies:
|
||||
hashery: 1.4.0
|
||||
@ -3902,6 +4101,17 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.1
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.28.0
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.28.0': {}
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.34.0': {}
|
||||
|
||||
'@oxc-project/types@0.107.0': {}
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.11.0':
|
||||
@ -4199,6 +4409,8 @@ snapshots:
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@tootallnate/once@2.0.0': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@ -4345,7 +4557,7 @@ snapshots:
|
||||
'@vitest/mocker': 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))
|
||||
playwright: 1.57.0
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- msw
|
||||
@ -4357,7 +4569,7 @@ snapshots:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@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)
|
||||
vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- msw
|
||||
@ -4374,7 +4586,7 @@ snapshots:
|
||||
pngjs: 7.0.0
|
||||
sirv: 3.0.2
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@ -4395,7 +4607,7 @@ snapshots:
|
||||
obug: 2.1.1
|
||||
std-env: 3.10.0
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
transitivePeerDependencies:
|
||||
@ -4501,6 +4713,12 @@ snapshots:
|
||||
mime-types: 3.0.2
|
||||
negotiator: 1.0.0
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
@ -4543,6 +4761,8 @@ snapshots:
|
||||
dequal: 2.0.3
|
||||
optional: true
|
||||
|
||||
arrify@2.0.1: {}
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
ast-v8-to-istanbul@0.3.10:
|
||||
@ -4709,6 +4929,12 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
codec-parser@2.5.0:
|
||||
@ -4798,6 +5024,13 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
duplexify@4.1.3:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
stream-shift: 1.0.3
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
@ -4812,6 +5045,10 @@ snapshots:
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
@ -5072,8 +5309,41 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
google-gax@5.0.6:
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
'@grpc/proto-loader': 0.8.0
|
||||
duplexify: 4.1.3
|
||||
google-auth-library: 10.5.0
|
||||
google-logging-utils: 1.1.3
|
||||
node-fetch: 3.3.2
|
||||
object-hash: 3.0.0
|
||||
proto3-json-serializer: 3.0.4
|
||||
protobufjs: 7.5.4
|
||||
retry-request: 8.0.2
|
||||
rimraf: 5.0.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
google-logging-utils@1.1.3: {}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
gaxios: 7.1.3
|
||||
google-auth-library: 10.5.0
|
||||
qs: 6.14.1
|
||||
url-template: 2.0.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
googleapis@170.0.0:
|
||||
dependencies:
|
||||
google-auth-library: 10.5.0
|
||||
googleapis-common: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
@ -5111,6 +5381,8 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
heap-js@2.7.1: {}
|
||||
|
||||
highlight.js@10.7.3: {}
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
@ -5132,6 +5404,14 @@ snapshots:
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
dependencies:
|
||||
'@tootallnate/once': 2.0.0
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@ -5139,6 +5419,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@ -5176,6 +5463,8 @@ snapshots:
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
is-stream-ended@0.1.4: {}
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-url@1.2.4: {}
|
||||
@ -5367,6 +5656,8 @@ snapshots:
|
||||
lit-element: 4.2.2
|
||||
lit-html: 3.3.2
|
||||
|
||||
lodash.camelcase@4.3.0: {}
|
||||
|
||||
lodash.clonedeep@4.5.0: {}
|
||||
|
||||
lodash.includes@4.3.0: {}
|
||||
@ -5383,6 +5674,8 @@ snapshots:
|
||||
|
||||
lodash.once@4.1.1: {}
|
||||
|
||||
lodash.snakecase@4.1.1: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
long@4.0.0: {}
|
||||
@ -5529,6 +5822,8 @@ snapshots:
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-hash@3.0.0: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
object-path@0.11.8: {}
|
||||
@ -5590,6 +5885,8 @@ snapshots:
|
||||
'@oxlint/win32-x64': 1.38.0
|
||||
oxlint-tsgolint: 0.11.0
|
||||
|
||||
p-defer@3.0.0: {}
|
||||
|
||||
p-finally@1.0.0: {}
|
||||
|
||||
p-queue@6.6.2:
|
||||
@ -5717,6 +6014,10 @@ snapshots:
|
||||
retry: 0.12.0
|
||||
signal-exit: 3.0.7
|
||||
|
||||
proto3-json-serializer@3.0.4:
|
||||
dependencies:
|
||||
protobufjs: 7.5.4
|
||||
|
||||
protobufjs@6.8.8:
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
@ -5817,6 +6118,12 @@ snapshots:
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@4.5.2:
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
@ -5839,6 +6146,13 @@ snapshots:
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
retry-request@8.0.2:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
teeny-request: 10.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
retry@0.12.0: {}
|
||||
|
||||
retry@0.13.1: {}
|
||||
@ -6061,6 +6375,12 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
stream-events@1.0.5:
|
||||
dependencies:
|
||||
stubs: 3.0.0
|
||||
|
||||
stream-shift@1.0.3: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@ -6093,6 +6413,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
|
||||
stubs@3.0.0: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
@ -6107,6 +6429,15 @@ snapshots:
|
||||
|
||||
tailwindcss@4.1.17: {}
|
||||
|
||||
teeny-request@10.1.0:
|
||||
dependencies:
|
||||
http-proxy-agent: 5.0.0
|
||||
https-proxy-agent: 5.0.1
|
||||
node-fetch: 3.3.2
|
||||
stream-events: 1.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
@ -6197,6 +6528,8 @@ snapshots:
|
||||
|
||||
urijs@1.19.11: {}
|
||||
|
||||
url-template@2.0.8: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
uuid@11.1.0: {}
|
||||
@ -6221,7 +6554,7 @@ snapshots:
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.2
|
||||
|
||||
vitest@4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2):
|
||||
vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(@vitest/browser-preview@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.0.16
|
||||
'@vitest/mocker': 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))
|
||||
@ -6244,6 +6577,7 @@ snapshots:
|
||||
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)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@types/node': 25.0.3
|
||||
'@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)
|
||||
'@vitest/browser-preview': 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)
|
||||
@ -6317,6 +6651,8 @@ snapshots:
|
||||
|
||||
yargs-parser@20.2.9: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@16.2.0:
|
||||
dependencies:
|
||||
cliui: 7.0.4
|
||||
@ -6327,6 +6663,16 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 20.2.9
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
@ -746,6 +746,40 @@ export type MSTeamsTeamConfig = {
|
||||
channels?: Record<string, MSTeamsChannelConfig>;
|
||||
};
|
||||
|
||||
export type GoogleChatAccountConfig = {
|
||||
/** Optional display name for this account (used in CLI/UI lists). */
|
||||
name?: string;
|
||||
/** If false, do not start this Google Chat account. Default: true. */
|
||||
enabled?: boolean;
|
||||
/** Google Cloud Project ID. */
|
||||
projectId?: string;
|
||||
/** Pub/Sub subscription name (full path: projects/.../subscriptions/...). */
|
||||
subscriptionName?: string;
|
||||
/** Path to service account credentials JSON file. */
|
||||
credentialsPath?: string;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/** Allowlist for DM senders (email addresses). */
|
||||
allowFrom?: string[];
|
||||
/** Group/space access policy (default: disabled). */
|
||||
spacePolicy?: GroupPolicy;
|
||||
/** Allowlist for spaces (space IDs). */
|
||||
allowSpaces?: string[];
|
||||
/** Max space messages to keep as history context (0 disables). */
|
||||
historyLimit?: number;
|
||||
/** Max DM turns to keep as history context. */
|
||||
dmHistoryLimit?: number;
|
||||
/** Outbound text chunk size (chars). Default: 4000. */
|
||||
textChunkLimit?: number;
|
||||
/** Outbound message prefix. */
|
||||
messagePrefix?: string;
|
||||
};
|
||||
|
||||
export type GoogleChatConfig = {
|
||||
/** Optional per-account Google Chat configuration (multi-account). */
|
||||
accounts?: Record<string, GoogleChatAccountConfig>;
|
||||
} & GoogleChatAccountConfig;
|
||||
|
||||
export type MSTeamsConfig = {
|
||||
/** If false, do not start the MS Teams provider. Default: true. */
|
||||
enabled?: boolean;
|
||||
@ -1655,6 +1689,7 @@ export type ClawdbotConfig = {
|
||||
signal?: SignalConfig;
|
||||
imessage?: IMessageConfig;
|
||||
msteams?: MSTeamsConfig;
|
||||
googlechat?: GoogleChatConfig;
|
||||
cron?: CronConfig;
|
||||
hooks?: HooksConfig;
|
||||
bridge?: BridgeConfig;
|
||||
|
||||
89
src/googlechat/accounts.ts
Normal file
89
src/googlechat/accounts.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../routing/session-key.js";
|
||||
import type { GoogleChatAccountConfig } from "./types.js";
|
||||
|
||||
export type ResolvedGoogleChatAccount = {
|
||||
accountId: string;
|
||||
enabled: boolean;
|
||||
name?: string;
|
||||
projectId: string;
|
||||
subscriptionName: string;
|
||||
credentialsPath?: string;
|
||||
config: GoogleChatAccountConfig;
|
||||
};
|
||||
|
||||
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const googlechat = (cfg as { googlechat?: { accounts?: Record<string, unknown> } }).googlechat;
|
||||
const accounts = googlechat?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return [];
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
|
||||
export function listGoogleChatAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||
return ids.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
export function resolveDefaultGoogleChatAccountId(cfg: ClawdbotConfig): string {
|
||||
const ids = listGoogleChatAccountIds(cfg);
|
||||
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
|
||||
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
function resolveAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): GoogleChatAccountConfig | undefined {
|
||||
const googlechat = (cfg as { googlechat?: { accounts?: Record<string, GoogleChatAccountConfig> } }).googlechat;
|
||||
const accounts = googlechat?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return undefined;
|
||||
return accounts[accountId];
|
||||
}
|
||||
|
||||
function mergeGoogleChatAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): GoogleChatAccountConfig {
|
||||
const googlechat = (cfg as { googlechat?: GoogleChatAccountConfig & { accounts?: unknown } }).googlechat ?? {};
|
||||
const { accounts: _ignored, ...base } = googlechat;
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
return { ...base, ...account };
|
||||
}
|
||||
|
||||
export function resolveGoogleChatAccount(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
}): ResolvedGoogleChatAccount {
|
||||
const googlechat = (params.cfg as { googlechat?: { enabled?: boolean } }).googlechat;
|
||||
const baseEnabled = googlechat?.enabled !== false;
|
||||
|
||||
const resolve = (accountId: string) => {
|
||||
const merged = mergeGoogleChatAccountConfig(params.cfg, accountId);
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const enabled = baseEnabled && accountEnabled;
|
||||
return {
|
||||
accountId,
|
||||
enabled,
|
||||
name: merged.name?.trim() || undefined,
|
||||
projectId: merged.projectId ?? "",
|
||||
subscriptionName: merged.subscriptionName ?? "",
|
||||
credentialsPath: merged.credentialsPath,
|
||||
config: merged,
|
||||
} satisfies ResolvedGoogleChatAccount;
|
||||
};
|
||||
|
||||
const normalized = normalizeAccountId(params.accountId);
|
||||
return resolve(normalized);
|
||||
}
|
||||
|
||||
export function listEnabledGoogleChatAccounts(
|
||||
cfg: ClawdbotConfig,
|
||||
): ResolvedGoogleChatAccount[] {
|
||||
return listGoogleChatAccountIds(cfg)
|
||||
.map((accountId) => resolveGoogleChatAccount({ cfg, accountId }))
|
||||
.filter((account) => account.enabled);
|
||||
}
|
||||
184
src/googlechat/monitor.ts
Normal file
184
src/googlechat/monitor.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import { PubSub, type Message } from "@google-cloud/pubsub";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
import type { GoogleChatEvent, GoogleChatMessage } from "./types.js";
|
||||
|
||||
export type GoogleChatMonitorOptions = {
|
||||
account: ResolvedGoogleChatAccount;
|
||||
config: ClawdbotConfig;
|
||||
runtime: RuntimeEnv;
|
||||
abortSignal: AbortSignal;
|
||||
};
|
||||
|
||||
export type NormalizedGoogleChatMessage = {
|
||||
provider: "googlechat";
|
||||
accountId: string;
|
||||
messageId: string;
|
||||
timestamp: number;
|
||||
sender: {
|
||||
id: string;
|
||||
name: string;
|
||||
email?: string;
|
||||
};
|
||||
chat: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "dm" | "group";
|
||||
};
|
||||
thread?: {
|
||||
id: string;
|
||||
};
|
||||
content: {
|
||||
text: string;
|
||||
};
|
||||
raw: GoogleChatEvent;
|
||||
};
|
||||
|
||||
function normalizeMessage(
|
||||
event: GoogleChatEvent,
|
||||
accountId: string,
|
||||
): NormalizedGoogleChatMessage | null {
|
||||
if (event.type !== "MESSAGE" || !event.message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const msg = event.message;
|
||||
|
||||
// Skip messages from bots
|
||||
if (msg.sender.type === "BOT") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const spaceId = msg.space.name.replace("spaces/", "");
|
||||
const isDM = msg.space.type === "DM";
|
||||
|
||||
return {
|
||||
provider: "googlechat",
|
||||
accountId,
|
||||
messageId: msg.name,
|
||||
timestamp: new Date(msg.createTime).getTime(),
|
||||
sender: {
|
||||
id: msg.sender.name.replace("users/", ""),
|
||||
name: msg.sender.displayName,
|
||||
email: msg.sender.email,
|
||||
},
|
||||
chat: {
|
||||
id: spaceId,
|
||||
name: msg.space.displayName || spaceId,
|
||||
type: isDM ? "dm" : "group",
|
||||
},
|
||||
thread: msg.thread
|
||||
? {
|
||||
id: msg.thread.name,
|
||||
}
|
||||
: undefined,
|
||||
content: {
|
||||
text: msg.argumentText || msg.text || "",
|
||||
},
|
||||
raw: event,
|
||||
};
|
||||
}
|
||||
|
||||
function checkMessagePolicy(
|
||||
message: NormalizedGoogleChatMessage,
|
||||
account: ResolvedGoogleChatAccount,
|
||||
): boolean {
|
||||
const policy =
|
||||
message.chat.type === "dm"
|
||||
? account.config.dmPolicy ?? "pairing"
|
||||
: account.config.spacePolicy ?? "disabled";
|
||||
|
||||
switch (policy) {
|
||||
case "disabled":
|
||||
return false;
|
||||
case "open":
|
||||
return true;
|
||||
case "allowlist": {
|
||||
if (message.chat.type === "dm") {
|
||||
const allowFrom = account.config.allowFrom ?? [];
|
||||
return allowFrom.includes(message.sender.email ?? "");
|
||||
}
|
||||
const allowSpaces = account.config.allowSpaces ?? [];
|
||||
return allowSpaces.includes(message.chat.id);
|
||||
}
|
||||
case "pairing":
|
||||
// For pairing mode, we'd need to check the pairing store
|
||||
// For now, treat as allowlist-based
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function monitorGoogleChatProvider(
|
||||
options: GoogleChatMonitorOptions,
|
||||
): Promise<() => void> {
|
||||
const { account, abortSignal } = options;
|
||||
|
||||
if (!account.projectId || !account.subscriptionName) {
|
||||
throw new Error(
|
||||
`Google Chat account ${account.accountId} missing projectId or subscriptionName`,
|
||||
);
|
||||
}
|
||||
|
||||
const pubsub = new PubSub({
|
||||
projectId: account.projectId,
|
||||
keyFilename: account.credentialsPath,
|
||||
});
|
||||
|
||||
const subscription = pubsub.subscription(account.subscriptionName);
|
||||
|
||||
const messageHandler = async (message: Message) => {
|
||||
try {
|
||||
const eventData = JSON.parse(message.data.toString()) as GoogleChatEvent;
|
||||
|
||||
const normalized = normalizeMessage(eventData, account.accountId);
|
||||
|
||||
if (normalized) {
|
||||
const allowed = checkMessagePolicy(normalized, account);
|
||||
if (allowed) {
|
||||
// Route to agent - this will be wired up by the gateway
|
||||
// For now, we just acknowledge the message
|
||||
console.log(
|
||||
`[googlechat:${account.accountId}] Received message from ${normalized.sender.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
message.ack();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[googlechat:${account.accountId}] Error processing message:`,
|
||||
error,
|
||||
);
|
||||
message.nack();
|
||||
}
|
||||
};
|
||||
|
||||
const errorHandler = (error: Error) => {
|
||||
console.error(`[googlechat:${account.accountId}] Subscription error:`, error);
|
||||
};
|
||||
|
||||
subscription.on("message", messageHandler);
|
||||
subscription.on("error", errorHandler);
|
||||
|
||||
// Handle abort signal
|
||||
const cleanup = () => {
|
||||
subscription.removeListener("message", messageHandler);
|
||||
subscription.removeListener("error", errorHandler);
|
||||
console.log(`[googlechat:${account.accountId}] Monitor stopped`);
|
||||
};
|
||||
|
||||
if (abortSignal.aborted) {
|
||||
cleanup();
|
||||
} else {
|
||||
abortSignal.addEventListener("abort", cleanup, { once: true });
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[googlechat:${account.accountId}] Listening on subscription: ${account.subscriptionName}`,
|
||||
);
|
||||
|
||||
return cleanup;
|
||||
}
|
||||
69
src/googlechat/probe.ts
Normal file
69
src/googlechat/probe.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { PubSub } from "@google-cloud/pubsub";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
export type GoogleChatProbeResult = {
|
||||
ok: boolean;
|
||||
status: string;
|
||||
projectId?: string;
|
||||
subscriptionName?: string;
|
||||
topic?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export async function probeGoogleChat(
|
||||
account: ResolvedGoogleChatAccount,
|
||||
timeoutMs = 5000,
|
||||
): Promise<GoogleChatProbeResult> {
|
||||
if (!account.projectId || !account.subscriptionName) {
|
||||
return {
|
||||
ok: false,
|
||||
status: "Not configured",
|
||||
error: "Missing projectId or subscriptionName",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const pubsub = new PubSub({
|
||||
projectId: account.projectId,
|
||||
keyFilename: account.credentialsPath,
|
||||
});
|
||||
|
||||
const subscription = pubsub.subscription(account.subscriptionName);
|
||||
|
||||
// Use a timeout for the exists check
|
||||
const existsPromise = subscription.exists();
|
||||
const timeoutPromise = new Promise<[boolean]>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Timeout")), timeoutMs),
|
||||
);
|
||||
|
||||
const [exists] = await Promise.race([existsPromise, timeoutPromise]);
|
||||
|
||||
if (!exists) {
|
||||
return {
|
||||
ok: false,
|
||||
status: "Subscription not found",
|
||||
projectId: account.projectId,
|
||||
subscriptionName: account.subscriptionName,
|
||||
};
|
||||
}
|
||||
|
||||
const [metadata] = await subscription.getMetadata();
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
status: "Connected",
|
||||
projectId: account.projectId,
|
||||
subscriptionName: account.subscriptionName,
|
||||
topic: metadata.topic ?? undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
ok: false,
|
||||
status: "Connection failed",
|
||||
projectId: account.projectId,
|
||||
subscriptionName: account.subscriptionName,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
171
src/googlechat/send.ts
Normal file
171
src/googlechat/send.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { google, type chat_v1 } from "googleapis";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
let chatClients: Map<string, chat_v1.Chat> = new Map();
|
||||
|
||||
async function getChatClient(account: ResolvedGoogleChatAccount): Promise<chat_v1.Chat> {
|
||||
const cacheKey = `${account.accountId}:${account.credentialsPath ?? "default"}`;
|
||||
const cached = chatClients.get(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const auth = new google.auth.GoogleAuth({
|
||||
keyFile: account.credentialsPath,
|
||||
scopes: ["https://www.googleapis.com/auth/chat.bot"],
|
||||
});
|
||||
|
||||
const client = google.chat({
|
||||
version: "v1",
|
||||
auth,
|
||||
});
|
||||
|
||||
chatClients.set(cacheKey, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
export type SendGoogleChatResult = {
|
||||
messageId: string;
|
||||
spaceName: string;
|
||||
};
|
||||
|
||||
export async function sendGoogleChatText(
|
||||
to: string,
|
||||
text: string,
|
||||
options: {
|
||||
account: ResolvedGoogleChatAccount;
|
||||
threadKey?: string;
|
||||
replyToId?: string;
|
||||
},
|
||||
): Promise<SendGoogleChatResult> {
|
||||
const client = await getChatClient(options.account);
|
||||
|
||||
const spaceName = to.startsWith("spaces/") ? to : `spaces/${to}`;
|
||||
|
||||
const prefix = options.account.config.messagePrefix;
|
||||
const formattedText = prefix ? `${prefix} ${text}` : text;
|
||||
|
||||
const requestBody: chat_v1.Schema$Message = {
|
||||
text: formattedText,
|
||||
};
|
||||
|
||||
if (options.threadKey) {
|
||||
requestBody.thread = { name: options.threadKey };
|
||||
}
|
||||
|
||||
const response = await client.spaces.messages.create({
|
||||
parent: spaceName,
|
||||
requestBody,
|
||||
});
|
||||
|
||||
return {
|
||||
messageId: response.data.name ?? "",
|
||||
spaceName,
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendGoogleChatCard(
|
||||
to: string,
|
||||
card: chat_v1.Schema$CardWithId,
|
||||
options: {
|
||||
account: ResolvedGoogleChatAccount;
|
||||
threadKey?: string;
|
||||
text?: string;
|
||||
},
|
||||
): Promise<SendGoogleChatResult> {
|
||||
const client = await getChatClient(options.account);
|
||||
|
||||
const spaceName = to.startsWith("spaces/") ? to : `spaces/${to}`;
|
||||
|
||||
const requestBody: chat_v1.Schema$Message = {
|
||||
cardsV2: [card],
|
||||
};
|
||||
|
||||
if (options.text) {
|
||||
requestBody.text = options.text;
|
||||
}
|
||||
|
||||
if (options.threadKey) {
|
||||
requestBody.thread = { name: options.threadKey };
|
||||
}
|
||||
|
||||
const response = await client.spaces.messages.create({
|
||||
parent: spaceName,
|
||||
requestBody,
|
||||
});
|
||||
|
||||
return {
|
||||
messageId: response.data.name ?? "",
|
||||
spaceName,
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendGoogleChatMedia(
|
||||
to: string,
|
||||
mediaUrl: string,
|
||||
options: {
|
||||
account: ResolvedGoogleChatAccount;
|
||||
caption?: string;
|
||||
threadKey?: string;
|
||||
},
|
||||
): Promise<SendGoogleChatResult> {
|
||||
// Google Chat doesn't support direct media upload via API for bots
|
||||
// Send as a card with image
|
||||
const card: chat_v1.Schema$CardWithId = {
|
||||
cardId: `media-${Date.now()}`,
|
||||
card: {
|
||||
sections: [
|
||||
{
|
||||
widgets: [
|
||||
{
|
||||
image: {
|
||||
imageUrl: mediaUrl,
|
||||
altText: options.caption ?? "Image",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (options.caption) {
|
||||
card.card!.sections![0].widgets!.unshift({
|
||||
textParagraph: { text: options.caption },
|
||||
});
|
||||
}
|
||||
|
||||
return sendGoogleChatCard(to, card, {
|
||||
account: options.account,
|
||||
threadKey: options.threadKey,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk text for Google Chat (4096 char limit).
|
||||
*/
|
||||
export function chunkGoogleChatText(text: string, maxLength = 4000): string[] {
|
||||
if (text.length <= maxLength) return [text];
|
||||
|
||||
const chunks: string[] = [];
|
||||
let remaining = text;
|
||||
|
||||
while (remaining.length > 0) {
|
||||
if (remaining.length <= maxLength) {
|
||||
chunks.push(remaining);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find last newline or space within limit
|
||||
let splitAt = remaining.lastIndexOf("\n", maxLength);
|
||||
if (splitAt === -1 || splitAt < maxLength / 2) {
|
||||
splitAt = remaining.lastIndexOf(" ", maxLength);
|
||||
}
|
||||
if (splitAt === -1 || splitAt < maxLength / 2) {
|
||||
splitAt = maxLength;
|
||||
}
|
||||
|
||||
chunks.push(remaining.slice(0, splitAt));
|
||||
remaining = remaining.slice(splitAt).trimStart();
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
79
src/googlechat/types.ts
Normal file
79
src/googlechat/types.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { DmPolicy, GroupPolicy } from "../config/types.js";
|
||||
|
||||
export type GoogleChatAccountConfig = {
|
||||
/** Optional display name for this account (used in CLI/UI lists). */
|
||||
name?: string;
|
||||
/** If false, do not start this Google Chat account. Default: true. */
|
||||
enabled?: boolean;
|
||||
/** Google Cloud Project ID. */
|
||||
projectId?: string;
|
||||
/** Pub/Sub subscription name (full path: projects/.../subscriptions/...). */
|
||||
subscriptionName?: string;
|
||||
/** Path to service account credentials JSON file. */
|
||||
credentialsPath?: string;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/** Allowlist for DM senders (email addresses). */
|
||||
allowFrom?: string[];
|
||||
/** Group/space access policy (default: disabled). */
|
||||
spacePolicy?: GroupPolicy;
|
||||
/** Allowlist for spaces (space IDs). */
|
||||
allowSpaces?: string[];
|
||||
/** Max space messages to keep as history context (0 disables). */
|
||||
historyLimit?: number;
|
||||
/** Max DM turns to keep as history context. */
|
||||
dmHistoryLimit?: number;
|
||||
/** Outbound text chunk size (chars). Default: 4000. */
|
||||
textChunkLimit?: number;
|
||||
/** Outbound message prefix. */
|
||||
messagePrefix?: string;
|
||||
};
|
||||
|
||||
export type GoogleChatConfig = {
|
||||
/** Optional per-account Google Chat configuration (multi-account). */
|
||||
accounts?: Record<string, GoogleChatAccountConfig>;
|
||||
} & GoogleChatAccountConfig;
|
||||
|
||||
export type GoogleChatMessage = {
|
||||
name: string;
|
||||
sender: {
|
||||
name: string;
|
||||
displayName: string;
|
||||
email?: string;
|
||||
type: "HUMAN" | "BOT";
|
||||
};
|
||||
createTime: string;
|
||||
text?: string;
|
||||
space: {
|
||||
name: string;
|
||||
type: "DM" | "ROOM" | "SPACE";
|
||||
displayName?: string;
|
||||
};
|
||||
thread?: {
|
||||
name: string;
|
||||
};
|
||||
argumentText?: string;
|
||||
slashCommand?: {
|
||||
commandId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type GoogleChatEvent = {
|
||||
type:
|
||||
| "MESSAGE"
|
||||
| "ADDED_TO_SPACE"
|
||||
| "REMOVED_FROM_SPACE"
|
||||
| "CARD_CLICKED";
|
||||
eventTime: string;
|
||||
message?: GoogleChatMessage;
|
||||
user?: {
|
||||
name: string;
|
||||
displayName: string;
|
||||
email?: string;
|
||||
};
|
||||
space?: {
|
||||
name: string;
|
||||
type: string;
|
||||
displayName?: string;
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveDiscordAccount } from "../discord/accounts.js";
|
||||
import { resolveGoogleChatAccount } from "../googlechat/accounts.js";
|
||||
import { resolveIMessageAccount } from "../imessage/accounts.js";
|
||||
import { resolveSignalAccount } from "../signal/accounts.js";
|
||||
import { resolveSlackAccount } from "../slack/accounts.js";
|
||||
@ -281,6 +282,26 @@ const DOCKS: Record<ProviderId, ProviderDock> = {
|
||||
formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom),
|
||||
},
|
||||
},
|
||||
googlechat: {
|
||||
id: "googlechat",
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group", "thread"],
|
||||
threads: true,
|
||||
media: true,
|
||||
},
|
||||
outbound: { textChunkLimit: 4000 },
|
||||
config: {
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
resolveGoogleChatAccount({ cfg, accountId }).config.allowFrom ?? [],
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
},
|
||||
threading: {
|
||||
resolveReplyToMode: () => "first",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function listProviderDocks(): ProviderDock[] {
|
||||
|
||||
191
src/providers/plugins/googlechat.ts
Normal file
191
src/providers/plugins/googlechat.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { chunkMarkdownText } from "../../auto-reply/chunk.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
import {
|
||||
listGoogleChatAccountIds,
|
||||
type ResolvedGoogleChatAccount,
|
||||
resolveDefaultGoogleChatAccountId,
|
||||
resolveGoogleChatAccount,
|
||||
} from "../../googlechat/accounts.js";
|
||||
import { probeGoogleChat } from "../../googlechat/probe.js";
|
||||
import {
|
||||
chunkGoogleChatText,
|
||||
sendGoogleChatMedia,
|
||||
sendGoogleChatText,
|
||||
} from "../../googlechat/send.js";
|
||||
import { getChatProviderMeta } from "../registry.js";
|
||||
import {
|
||||
deleteAccountFromConfigSection,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "./config-helpers.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import type { ProviderPlugin } from "./types.js";
|
||||
|
||||
const meta = getChatProviderMeta("googlechat");
|
||||
|
||||
export const googlechatPlugin: ProviderPlugin<ResolvedGoogleChatAccount> = {
|
||||
id: "googlechat",
|
||||
meta: {
|
||||
...meta,
|
||||
quickstartAllowFrom: true,
|
||||
},
|
||||
pairing: {
|
||||
idLabel: "email",
|
||||
normalizeAllowEntry: (entry) => entry.toLowerCase().trim(),
|
||||
},
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group", "thread"],
|
||||
threads: true,
|
||||
media: true,
|
||||
},
|
||||
reload: { configPrefixes: ["googlechat"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listGoogleChatAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveGoogleChatAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultGoogleChatAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
cfg,
|
||||
sectionKey: "googlechat",
|
||||
accountId,
|
||||
enabled,
|
||||
allowTopLevel: true,
|
||||
}),
|
||||
deleteAccount: ({ cfg, accountId }) =>
|
||||
deleteAccountFromConfigSection({
|
||||
cfg,
|
||||
sectionKey: "googlechat",
|
||||
accountId,
|
||||
clearBaseFields: ["projectId", "subscriptionName", "credentialsPath", "name"],
|
||||
}),
|
||||
isConfigured: (account) =>
|
||||
Boolean(account.projectId?.trim() && account.subscriptionName?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured: Boolean(
|
||||
account.projectId?.trim() && account.subscriptionName?.trim(),
|
||||
),
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
resolveGoogleChatAccount({ cfg, accountId }).config.allowFrom ?? [],
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const googlechat = (cfg as { googlechat?: { accounts?: Record<string, unknown> } }).googlechat;
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(googlechat?.accounts?.[resolvedAccountId]);
|
||||
const basePath = useAccountPath
|
||||
? `googlechat.accounts.${resolvedAccountId}.`
|
||||
: "googlechat.";
|
||||
return {
|
||||
policy: account.config.dmPolicy ?? "pairing",
|
||||
allowFrom: account.config.allowFrom ?? [],
|
||||
policyPath: `${basePath}dmPolicy`,
|
||||
allowFromPath: basePath,
|
||||
approveHint: formatPairingApproveHint("googlechat"),
|
||||
normalizeEntry: (raw) => raw.toLowerCase().trim(),
|
||||
};
|
||||
},
|
||||
},
|
||||
threading: {
|
||||
resolveReplyToMode: () => "first",
|
||||
},
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
chunker: chunkGoogleChatText,
|
||||
textChunkLimit: 4000,
|
||||
resolveTarget: ({ to }) => {
|
||||
const trimmed = to?.trim();
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to Google Chat requires --to <spaceId>",
|
||||
),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
},
|
||||
sendText: async ({ to, text, accountId, cfg, replyToId }) => {
|
||||
const account = resolveGoogleChatAccount({ cfg, accountId });
|
||||
const result = await sendGoogleChatText(to, text, {
|
||||
account,
|
||||
threadKey: replyToId ?? undefined,
|
||||
});
|
||||
return { provider: "googlechat", ...result };
|
||||
},
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, cfg, replyToId }) => {
|
||||
const account = resolveGoogleChatAccount({ cfg, accountId });
|
||||
const result = await sendGoogleChatMedia(to, mediaUrl ?? "", {
|
||||
account,
|
||||
caption: text,
|
||||
threadKey: replyToId ?? undefined,
|
||||
});
|
||||
return { provider: "googlechat", ...result };
|
||||
},
|
||||
},
|
||||
status: {
|
||||
defaultRuntime: {
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
},
|
||||
buildProviderSummary: ({ snapshot }) => ({
|
||||
configured: snapshot.configured ?? false,
|
||||
running: snapshot.running ?? false,
|
||||
lastStartAt: snapshot.lastStartAt ?? null,
|
||||
lastStopAt: snapshot.lastStopAt ?? null,
|
||||
lastError: snapshot.lastError ?? null,
|
||||
probe: snapshot.probe,
|
||||
lastProbeAt: snapshot.lastProbeAt ?? null,
|
||||
}),
|
||||
probeAccount: async ({ account, timeoutMs }) =>
|
||||
probeGoogleChat(account, timeoutMs),
|
||||
buildAccountSnapshot: ({ account, runtime, probe }) => {
|
||||
const configured = Boolean(
|
||||
account.projectId?.trim() && account.subscriptionName?.trim(),
|
||||
);
|
||||
return {
|
||||
accountId: account.accountId,
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured,
|
||||
running: runtime?.running ?? false,
|
||||
lastStartAt: runtime?.lastStartAt ?? null,
|
||||
lastStopAt: runtime?.lastStopAt ?? null,
|
||||
lastError: runtime?.lastError ?? null,
|
||||
probe,
|
||||
lastInboundAt: runtime?.lastInboundAt ?? null,
|
||||
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
||||
};
|
||||
},
|
||||
},
|
||||
gateway: {
|
||||
startAccount: async (ctx) => {
|
||||
const account = ctx.account;
|
||||
ctx.log?.info(`[${account.accountId}] starting Google Chat provider`);
|
||||
|
||||
// Lazy import to avoid circular deps
|
||||
const { monitorGoogleChatProvider } = await import(
|
||||
"../../googlechat/monitor.js"
|
||||
);
|
||||
|
||||
return monitorGoogleChatProvider({
|
||||
account,
|
||||
config: ctx.cfg,
|
||||
runtime: ctx.runtime,
|
||||
abortSignal: ctx.abortSignal,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -4,6 +4,7 @@ import {
|
||||
normalizeChatProviderId,
|
||||
} from "../registry.js";
|
||||
import { discordPlugin } from "./discord.js";
|
||||
import { googlechatPlugin } from "./googlechat.js";
|
||||
import { imessagePlugin } from "./imessage.js";
|
||||
import { msteamsPlugin } from "./msteams.js";
|
||||
import { signalPlugin } from "./signal.js";
|
||||
@ -31,6 +32,7 @@ function resolveProviders(): ProviderPlugin[] {
|
||||
signalPlugin,
|
||||
imessagePlugin,
|
||||
msteamsPlugin,
|
||||
googlechatPlugin,
|
||||
];
|
||||
}
|
||||
|
||||
@ -57,6 +59,7 @@ export function normalizeProviderId(raw?: string | null): ProviderId | null {
|
||||
|
||||
export {
|
||||
discordPlugin,
|
||||
googlechatPlugin,
|
||||
imessagePlugin,
|
||||
msteamsPlugin,
|
||||
signalPlugin,
|
||||
|
||||
@ -14,6 +14,7 @@ const LOADERS: Record<ProviderId, PluginLoader> = {
|
||||
signal: async () => (await import("./signal.js")).signalPlugin,
|
||||
imessage: async () => (await import("./imessage.js")).imessagePlugin,
|
||||
msteams: async () => (await import("./msteams.js")).msteamsPlugin,
|
||||
googlechat: async () => (await import("./googlechat.js")).googlechatPlugin,
|
||||
};
|
||||
|
||||
const cache = new Map<ProviderId, ProviderPlugin>();
|
||||
|
||||
40
src/providers/plugins/outbound/googlechat.ts
Normal file
40
src/providers/plugins/outbound/googlechat.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { resolveGoogleChatAccount } from "../../../googlechat/accounts.js";
|
||||
import {
|
||||
chunkGoogleChatText,
|
||||
sendGoogleChatMedia,
|
||||
sendGoogleChatText,
|
||||
} from "../../../googlechat/send.js";
|
||||
import type { ProviderOutboundAdapter } from "../types.js";
|
||||
|
||||
export const googlechatOutbound: ProviderOutboundAdapter = {
|
||||
deliveryMode: "direct",
|
||||
chunker: chunkGoogleChatText,
|
||||
textChunkLimit: 4000,
|
||||
resolveTarget: ({ to }) => {
|
||||
const trimmed = to?.trim();
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error("Delivering to Google Chat requires --to <spaceId>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
},
|
||||
sendText: async ({ to, text, accountId, cfg, replyToId }) => {
|
||||
const account = resolveGoogleChatAccount({ cfg, accountId });
|
||||
const result = await sendGoogleChatText(to, text, {
|
||||
account,
|
||||
threadKey: replyToId ?? undefined,
|
||||
});
|
||||
return { provider: "googlechat", ...result };
|
||||
},
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, cfg, replyToId }) => {
|
||||
const account = resolveGoogleChatAccount({ cfg, accountId });
|
||||
const result = await sendGoogleChatMedia(to, mediaUrl ?? "", {
|
||||
account,
|
||||
caption: text,
|
||||
threadKey: replyToId ?? undefined,
|
||||
});
|
||||
return { provider: "googlechat", ...result };
|
||||
},
|
||||
};
|
||||
@ -15,6 +15,7 @@ const LOADERS: Record<ProviderId, OutboundLoader> = {
|
||||
signal: async () => (await import("./signal.js")).signalOutbound,
|
||||
imessage: async () => (await import("./imessage.js")).imessageOutbound,
|
||||
msteams: async () => (await import("./msteams.js")).msteamsOutbound,
|
||||
googlechat: async () => (await import("./googlechat.js")).googlechatOutbound,
|
||||
};
|
||||
|
||||
const cache = new Map<ProviderId, ProviderOutboundAdapter>();
|
||||
|
||||
@ -8,6 +8,7 @@ export const CHAT_PROVIDER_ORDER = [
|
||||
"signal",
|
||||
"imessage",
|
||||
"msteams",
|
||||
"googlechat",
|
||||
] as const;
|
||||
|
||||
export type ChatProviderId = (typeof CHAT_PROVIDER_ORDER)[number];
|
||||
@ -94,11 +95,20 @@ const CHAT_PROVIDER_META: Record<ChatProviderId, ChatProviderMeta> = {
|
||||
docsLabel: "msteams",
|
||||
blurb: "supported (Bot Framework).",
|
||||
},
|
||||
googlechat: {
|
||||
id: "googlechat",
|
||||
label: "Google Chat",
|
||||
selectionLabel: "Google Chat (Pub/Sub)",
|
||||
docsPath: "/googlechat",
|
||||
docsLabel: "googlechat",
|
||||
blurb: "Google Workspace only; uses Pub/Sub for firewall-friendly operation.",
|
||||
},
|
||||
};
|
||||
|
||||
export const CHAT_PROVIDER_ALIASES: Record<string, ChatProviderId> = {
|
||||
imsg: "imessage",
|
||||
teams: "msteams",
|
||||
gchat: "googlechat",
|
||||
};
|
||||
|
||||
const normalizeProviderKey = (raw?: string | null): string | undefined => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user