Slack: add some fixes and connect it all up
This commit is contained in:
@@ -327,14 +327,13 @@ Slack runs in Socket Mode and requires both a bot token and app token:
|
|||||||
sessionPrefix: "slack:slash",
|
sessionPrefix: "slack:slash",
|
||||||
ephemeral: true
|
ephemeral: true
|
||||||
},
|
},
|
||||||
replyToMode: "off", // off | first | all
|
|
||||||
textChunkLimit: 4000,
|
textChunkLimit: 4000,
|
||||||
mediaMaxMb: 20
|
mediaMaxMb: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Clawdis starts Slack only when a `slack` config section exists and both tokens are set (unless `slack.enabled` is `false`). Provide `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` env vars if you prefer. Use `user:<id>` (DM) or `channel:<id>` when specifying delivery targets for cron/CLI commands.
|
Clawdis starts Slack when the provider is enabled and both tokens are set (via config or `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN`). Use `user:<id>` (DM) or `channel:<id>` when specifying delivery targets for cron/CLI commands.
|
||||||
|
|
||||||
Reaction notification modes:
|
Reaction notification modes:
|
||||||
- `off`: no reaction events.
|
- `off`: no reaction events.
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
|||||||
"sessionPrefix": "slack:slash",
|
"sessionPrefix": "slack:slash",
|
||||||
"ephemeral": true
|
"ephemeral": true
|
||||||
},
|
},
|
||||||
"replyToMode": "off",
|
|
||||||
"textChunkLimit": 4000,
|
"textChunkLimit": 4000,
|
||||||
"mediaMaxMb": 20
|
"mediaMaxMb": 20
|
||||||
}
|
}
|
||||||
@@ -137,13 +136,6 @@ Tokens can also be supplied via env vars:
|
|||||||
- Channels map to `slack:channel:<channelId>` sessions.
|
- Channels map to `slack:channel:<channelId>` sessions.
|
||||||
- Slash commands use `slack:slash:<userId>` sessions.
|
- Slash commands use `slack:slash:<userId>` sessions.
|
||||||
|
|
||||||
## Reply threading
|
|
||||||
Slack replies can be threaded when reply tags are present and `slack.replyToMode` is enabled.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "slack": { "replyToMode": "first" } }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Delivery targets
|
## Delivery targets
|
||||||
Use these with cron/CLI sends:
|
Use these with cron/CLI sends:
|
||||||
- `user:<id>` for DMs
|
- `user:<id>` for DMs
|
||||||
|
|||||||
@@ -79,6 +79,8 @@
|
|||||||
"@mariozechner/pi-ai": "^0.32.3",
|
"@mariozechner/pi-ai": "^0.32.3",
|
||||||
"@mariozechner/pi-coding-agent": "^0.32.3",
|
"@mariozechner/pi-coding-agent": "^0.32.3",
|
||||||
"@mariozechner/pi-tui": "^0.32.3",
|
"@mariozechner/pi-tui": "^0.32.3",
|
||||||
|
"@slack/bolt": "^4.5.0",
|
||||||
|
"@slack/web-api": "^7.11.1",
|
||||||
"@sinclair/typebox": "0.34.46",
|
"@sinclair/typebox": "0.34.46",
|
||||||
"@whiskeysockets/baileys": "7.0.0-rc.9",
|
"@whiskeysockets/baileys": "7.0.0-rc.9",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
|
|||||||
323
pnpm-lock.yaml
generated
323
pnpm-lock.yaml
generated
@@ -43,6 +43,12 @@ importers:
|
|||||||
'@sinclair/typebox':
|
'@sinclair/typebox':
|
||||||
specifier: 0.34.46
|
specifier: 0.34.46
|
||||||
version: 0.34.46
|
version: 0.34.46
|
||||||
|
'@slack/bolt':
|
||||||
|
specifier: ^4.5.0
|
||||||
|
version: 4.6.0(@types/express@5.0.6)
|
||||||
|
'@slack/web-api':
|
||||||
|
specifier: ^7.11.1
|
||||||
|
version: 7.13.0
|
||||||
'@whiskeysockets/baileys':
|
'@whiskeysockets/baileys':
|
||||||
specifier: 7.0.0-rc.9
|
specifier: 7.0.0-rc.9
|
||||||
version: 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5)
|
version: 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5)
|
||||||
@@ -1098,6 +1104,32 @@ packages:
|
|||||||
'@sinclair/typebox@0.34.46':
|
'@sinclair/typebox@0.34.46':
|
||||||
resolution: {integrity: sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ==}
|
resolution: {integrity: sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ==}
|
||||||
|
|
||||||
|
'@slack/bolt@4.6.0':
|
||||||
|
resolution: {integrity: sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ==}
|
||||||
|
engines: {node: '>=18', npm: '>=8.6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/express': ^5.0.0
|
||||||
|
|
||||||
|
'@slack/logger@4.0.0':
|
||||||
|
resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==}
|
||||||
|
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
||||||
|
|
||||||
|
'@slack/oauth@3.0.4':
|
||||||
|
resolution: {integrity: sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg==}
|
||||||
|
engines: {node: '>=18', npm: '>=8.6.0'}
|
||||||
|
|
||||||
|
'@slack/socket-mode@2.0.5':
|
||||||
|
resolution: {integrity: sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ==}
|
||||||
|
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
||||||
|
|
||||||
|
'@slack/types@2.19.0':
|
||||||
|
resolution: {integrity: sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA==}
|
||||||
|
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
|
||||||
|
|
||||||
|
'@slack/web-api@7.13.0':
|
||||||
|
resolution: {integrity: sha512-ERcExbWrnkDN8ovoWWe6Wgt/usanj1dWUd18dJLpctUI4mlPS0nKt81Joh8VI+OPbNnY1lIilVt9gdMBD9U2ig==}
|
||||||
|
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0':
|
'@standard-schema/spec@1.1.0':
|
||||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
@@ -1156,6 +1188,9 @@ packages:
|
|||||||
'@types/http-errors@2.0.5':
|
'@types/http-errors@2.0.5':
|
||||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||||
|
|
||||||
|
'@types/jsonwebtoken@9.0.10':
|
||||||
|
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
||||||
|
|
||||||
'@types/linkify-it@5.0.0':
|
'@types/linkify-it@5.0.0':
|
||||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||||
|
|
||||||
@@ -1171,6 +1206,9 @@ packages:
|
|||||||
'@types/mime-types@2.1.4':
|
'@types/mime-types@2.1.4':
|
||||||
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
|
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
|
||||||
|
|
||||||
|
'@types/ms@2.1.0':
|
||||||
|
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||||
|
|
||||||
'@types/node@10.17.60':
|
'@types/node@10.17.60':
|
||||||
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
|
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
|
||||||
|
|
||||||
@@ -1186,6 +1224,9 @@ packages:
|
|||||||
'@types/range-parser@1.2.7':
|
'@types/range-parser@1.2.7':
|
||||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||||
|
|
||||||
|
'@types/retry@0.12.0':
|
||||||
|
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
||||||
|
|
||||||
@@ -1360,6 +1401,9 @@ packages:
|
|||||||
async-mutex@0.5.0:
|
async-mutex@0.5.0:
|
||||||
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
|
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
atomic-sleep@1.0.0:
|
atomic-sleep@1.0.0:
|
||||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
@@ -1374,6 +1418,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==}
|
resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
axios@1.13.2:
|
||||||
|
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
@@ -1489,6 +1536,10 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
commander@14.0.2:
|
commander@14.0.2:
|
||||||
resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
|
resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -1543,6 +1594,10 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
depd@2.0.0:
|
depd@2.0.0:
|
||||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -1621,6 +1676,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
esbuild@0.27.2:
|
esbuild@0.27.2:
|
||||||
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1644,6 +1703,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
|
||||||
eventemitter3@5.0.1:
|
eventemitter3@5.0.1:
|
||||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||||
|
|
||||||
@@ -1700,10 +1762,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||||
engines: {node: '>= 18.0.0'}
|
engines: {node: '>= 18.0.0'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.11:
|
||||||
|
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
formdata-polyfill@4.0.10:
|
formdata-polyfill@4.0.10:
|
||||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
@@ -1800,6 +1875,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
hashery@1.4.0:
|
hashery@1.4.0:
|
||||||
resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==}
|
resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -1853,6 +1932,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
is-electron@2.2.2:
|
||||||
|
resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==}
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
is-extglob@2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1872,6 +1954,10 @@ packages:
|
|||||||
is-promise@4.0.0:
|
is-promise@4.0.0:
|
||||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||||
|
|
||||||
|
is-stream@2.0.1:
|
||||||
|
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
is-url@1.2.4:
|
is-url@1.2.4:
|
||||||
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
||||||
|
|
||||||
@@ -1935,6 +2021,10 @@ packages:
|
|||||||
jsonc-parser@3.3.1:
|
jsonc-parser@3.3.1:
|
||||||
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
|
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
|
||||||
|
|
||||||
|
jsonwebtoken@9.0.3:
|
||||||
|
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
|
||||||
|
engines: {node: '>=12', npm: '>=6'}
|
||||||
|
|
||||||
jszip@3.10.1:
|
jszip@3.10.1:
|
||||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||||
|
|
||||||
@@ -2036,6 +2126,27 @@ packages:
|
|||||||
lit@3.3.2:
|
lit@3.3.2:
|
||||||
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
|
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
|
||||||
|
|
||||||
|
lodash.includes@4.3.0:
|
||||||
|
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||||
|
|
||||||
|
lodash.isboolean@3.0.3:
|
||||||
|
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||||
|
|
||||||
|
lodash.isinteger@4.0.4:
|
||||||
|
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||||
|
|
||||||
|
lodash.isnumber@3.0.3:
|
||||||
|
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||||
|
|
||||||
|
lodash.isplainobject@4.0.6:
|
||||||
|
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||||
|
|
||||||
|
lodash.isstring@4.0.1:
|
||||||
|
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||||
|
|
||||||
|
lodash.once@4.1.1:
|
||||||
|
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||||
|
|
||||||
lodash.snakecase@4.1.1:
|
lodash.snakecase@4.1.1:
|
||||||
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
||||||
|
|
||||||
@@ -2115,10 +2226,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime-db@1.54.0:
|
mime-db@1.54.0:
|
||||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime-types@3.0.2:
|
mime-types@3.0.2:
|
||||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2164,9 +2283,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
node-addon-api@7.1.1:
|
|
||||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
|
||||||
|
|
||||||
node-domexception@1.0.0:
|
node-domexception@1.0.0:
|
||||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||||
engines: {node: '>=10.5.0'}
|
engines: {node: '>=10.5.0'}
|
||||||
@@ -2250,10 +2366,26 @@ packages:
|
|||||||
oxlint-tsgolint:
|
oxlint-tsgolint:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
p-finally@1.0.0:
|
||||||
|
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
p-queue@6.6.2:
|
||||||
|
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-queue@9.0.1:
|
p-queue@9.0.1:
|
||||||
resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==}
|
resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
p-retry@4.6.2:
|
||||||
|
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
p-timeout@3.2.0:
|
||||||
|
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-timeout@7.0.1:
|
p-timeout@7.0.1:
|
||||||
resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}
|
resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -2377,6 +2509,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
punycode.js@2.3.1:
|
punycode.js@2.3.1:
|
||||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2450,6 +2585,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
retry@0.13.1:
|
||||||
|
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
reusify@1.1.0:
|
reusify@1.1.0:
|
||||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
@@ -2697,6 +2836,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==}
|
resolution: {integrity: sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
tsscmp@1.0.6:
|
||||||
|
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
|
||||||
|
engines: {node: '>=0.6.x'}
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3626,6 +3769,71 @@ snapshots:
|
|||||||
|
|
||||||
'@sinclair/typebox@0.34.46': {}
|
'@sinclair/typebox@0.34.46': {}
|
||||||
|
|
||||||
|
'@slack/bolt@4.6.0(@types/express@5.0.6)':
|
||||||
|
dependencies:
|
||||||
|
'@slack/logger': 4.0.0
|
||||||
|
'@slack/oauth': 3.0.4
|
||||||
|
'@slack/socket-mode': 2.0.5
|
||||||
|
'@slack/types': 2.19.0
|
||||||
|
'@slack/web-api': 7.13.0
|
||||||
|
'@types/express': 5.0.6
|
||||||
|
axios: 1.13.2
|
||||||
|
express: 5.2.1
|
||||||
|
path-to-regexp: 8.3.0
|
||||||
|
raw-body: 3.0.2
|
||||||
|
tsscmp: 1.0.6
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- debug
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@slack/logger@4.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
|
'@slack/oauth@3.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@slack/logger': 4.0.0
|
||||||
|
'@slack/web-api': 7.13.0
|
||||||
|
'@types/jsonwebtoken': 9.0.10
|
||||||
|
'@types/node': 25.0.3
|
||||||
|
jsonwebtoken: 9.0.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
|
'@slack/socket-mode@2.0.5':
|
||||||
|
dependencies:
|
||||||
|
'@slack/logger': 4.0.0
|
||||||
|
'@slack/web-api': 7.13.0
|
||||||
|
'@types/node': 25.0.3
|
||||||
|
'@types/ws': 8.18.1
|
||||||
|
eventemitter3: 5.0.1
|
||||||
|
ws: 8.18.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- debug
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@slack/types@2.19.0': {}
|
||||||
|
|
||||||
|
'@slack/web-api@7.13.0':
|
||||||
|
dependencies:
|
||||||
|
'@slack/logger': 4.0.0
|
||||||
|
'@slack/types': 2.19.0
|
||||||
|
'@types/node': 25.0.3
|
||||||
|
'@types/retry': 0.12.0
|
||||||
|
axios: 1.13.2
|
||||||
|
eventemitter3: 5.0.1
|
||||||
|
form-data: 4.0.5
|
||||||
|
is-electron: 2.2.2
|
||||||
|
is-stream: 2.0.1
|
||||||
|
p-queue: 6.6.2
|
||||||
|
p-retry: 4.6.2
|
||||||
|
retry: 0.13.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
@@ -3703,6 +3911,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/http-errors@2.0.5': {}
|
'@types/http-errors@2.0.5': {}
|
||||||
|
|
||||||
|
'@types/jsonwebtoken@9.0.10':
|
||||||
|
dependencies:
|
||||||
|
'@types/ms': 2.1.0
|
||||||
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/linkify-it@5.0.0': {}
|
'@types/linkify-it@5.0.0': {}
|
||||||
|
|
||||||
'@types/long@4.0.2': {}
|
'@types/long@4.0.2': {}
|
||||||
@@ -3716,6 +3929,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/mime-types@2.1.4': {}
|
'@types/mime-types@2.1.4': {}
|
||||||
|
|
||||||
|
'@types/ms@2.1.0': {}
|
||||||
|
|
||||||
'@types/node@10.17.60': {}
|
'@types/node@10.17.60': {}
|
||||||
|
|
||||||
'@types/node@25.0.3':
|
'@types/node@25.0.3':
|
||||||
@@ -3728,6 +3943,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/range-parser@1.2.7': {}
|
'@types/range-parser@1.2.7': {}
|
||||||
|
|
||||||
|
'@types/retry@0.12.0': {}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.0.3
|
'@types/node': 25.0.3
|
||||||
@@ -3961,6 +4178,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
atomic-sleep@1.0.0: {}
|
atomic-sleep@1.0.0: {}
|
||||||
|
|
||||||
audio-buffer@5.0.0:
|
audio-buffer@5.0.0:
|
||||||
@@ -3981,6 +4200,14 @@ snapshots:
|
|||||||
audio-type@2.2.1:
|
audio-type@2.2.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
axios@1.13.2:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.11
|
||||||
|
form-data: 4.0.5
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
balanced-match@3.0.1: {}
|
balanced-match@3.0.1: {}
|
||||||
@@ -4113,6 +4340,10 @@ snapshots:
|
|||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
commander@14.0.2: {}
|
commander@14.0.2: {}
|
||||||
|
|
||||||
commander@8.3.0: {}
|
commander@8.3.0: {}
|
||||||
@@ -4149,6 +4380,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3:
|
dequal@2.0.3:
|
||||||
@@ -4222,6 +4455,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
esbuild@0.27.2:
|
esbuild@0.27.2:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@esbuild/aix-ppc64': 0.27.2
|
'@esbuild/aix-ppc64': 0.27.2
|
||||||
@@ -4263,6 +4503,8 @@ snapshots:
|
|||||||
|
|
||||||
event-target-shim@5.0.1: {}
|
event-target-shim@5.0.1: {}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7: {}
|
||||||
|
|
||||||
eventemitter3@5.0.1: {}
|
eventemitter3@5.0.1: {}
|
||||||
|
|
||||||
events@3.3.0: {}
|
events@3.3.0: {}
|
||||||
@@ -4353,11 +4595,21 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
follow-redirects@1.15.11: {}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
formdata-polyfill@4.0.10:
|
formdata-polyfill@4.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
fetch-blob: 3.2.0
|
fetch-blob: 3.2.0
|
||||||
@@ -4478,6 +4730,10 @@ snapshots:
|
|||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
hashery@1.4.0:
|
hashery@1.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
hookified: 1.14.0
|
hookified: 1.14.0
|
||||||
@@ -4527,6 +4783,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions: 2.3.0
|
binary-extensions: 2.3.0
|
||||||
|
|
||||||
|
is-electron@2.2.2: {}
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
@@ -4539,6 +4797,8 @@ snapshots:
|
|||||||
|
|
||||||
is-promise@4.0.0: {}
|
is-promise@4.0.0: {}
|
||||||
|
|
||||||
|
is-stream@2.0.1: {}
|
||||||
|
|
||||||
is-url@1.2.4: {}
|
is-url@1.2.4: {}
|
||||||
|
|
||||||
isarray@1.0.0: {}
|
isarray@1.0.0: {}
|
||||||
@@ -4600,6 +4860,19 @@ snapshots:
|
|||||||
|
|
||||||
jsonc-parser@3.3.1: {}
|
jsonc-parser@3.3.1: {}
|
||||||
|
|
||||||
|
jsonwebtoken@9.0.3:
|
||||||
|
dependencies:
|
||||||
|
jws: 4.0.1
|
||||||
|
lodash.includes: 4.3.0
|
||||||
|
lodash.isboolean: 3.0.3
|
||||||
|
lodash.isinteger: 4.0.4
|
||||||
|
lodash.isnumber: 3.0.3
|
||||||
|
lodash.isplainobject: 4.0.6
|
||||||
|
lodash.isstring: 4.0.1
|
||||||
|
lodash.once: 4.1.1
|
||||||
|
ms: 2.1.3
|
||||||
|
semver: 7.7.3
|
||||||
|
|
||||||
jszip@3.10.1:
|
jszip@3.10.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
lie: 3.3.0
|
lie: 3.3.0
|
||||||
@@ -4700,6 +4973,20 @@ snapshots:
|
|||||||
lit-element: 4.2.2
|
lit-element: 4.2.2
|
||||||
lit-html: 3.3.2
|
lit-html: 3.3.2
|
||||||
|
|
||||||
|
lodash.includes@4.3.0: {}
|
||||||
|
|
||||||
|
lodash.isboolean@3.0.3: {}
|
||||||
|
|
||||||
|
lodash.isinteger@4.0.4: {}
|
||||||
|
|
||||||
|
lodash.isnumber@3.0.3: {}
|
||||||
|
|
||||||
|
lodash.isplainobject@4.0.6: {}
|
||||||
|
|
||||||
|
lodash.isstring@4.0.1: {}
|
||||||
|
|
||||||
|
lodash.once@4.1.1: {}
|
||||||
|
|
||||||
lodash.snakecase@4.1.1: {}
|
lodash.snakecase@4.1.1: {}
|
||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
@@ -4763,8 +5050,14 @@ snapshots:
|
|||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
mime-db@1.54.0: {}
|
mime-db@1.54.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
mime-types@3.0.2:
|
mime-types@3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.54.0
|
mime-db: 1.54.0
|
||||||
@@ -4814,8 +5107,6 @@ snapshots:
|
|||||||
|
|
||||||
negotiator@1.0.0: {}
|
negotiator@1.0.0: {}
|
||||||
|
|
||||||
node-addon-api@7.1.1: {}
|
|
||||||
|
|
||||||
node-domexception@1.0.0: {}
|
node-domexception@1.0.0: {}
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
@@ -4892,11 +5183,27 @@ snapshots:
|
|||||||
'@oxlint/win32-x64': 1.36.0
|
'@oxlint/win32-x64': 1.36.0
|
||||||
oxlint-tsgolint: 0.10.1
|
oxlint-tsgolint: 0.10.1
|
||||||
|
|
||||||
|
p-finally@1.0.0: {}
|
||||||
|
|
||||||
|
p-queue@6.6.2:
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
p-timeout: 3.2.0
|
||||||
|
|
||||||
p-queue@9.0.1:
|
p-queue@9.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 5.0.1
|
eventemitter3: 5.0.1
|
||||||
p-timeout: 7.0.1
|
p-timeout: 7.0.1
|
||||||
|
|
||||||
|
p-retry@4.6.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/retry': 0.12.0
|
||||||
|
retry: 0.13.1
|
||||||
|
|
||||||
|
p-timeout@3.2.0:
|
||||||
|
dependencies:
|
||||||
|
p-finally: 1.0.0
|
||||||
|
|
||||||
p-timeout@7.0.1: {}
|
p-timeout@7.0.1: {}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
@@ -5036,6 +5343,8 @@ snapshots:
|
|||||||
forwarded: 0.2.0
|
forwarded: 0.2.0
|
||||||
ipaddr.js: 1.9.1
|
ipaddr.js: 1.9.1
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
punycode.js@2.3.1: {}
|
punycode.js@2.3.1: {}
|
||||||
|
|
||||||
qified@0.5.3:
|
qified@0.5.3:
|
||||||
@@ -5122,6 +5431,8 @@ snapshots:
|
|||||||
|
|
||||||
retry@0.12.0: {}
|
retry@0.12.0: {}
|
||||||
|
|
||||||
|
retry@0.13.1: {}
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
reusify@1.1.0: {}
|
||||||
|
|
||||||
rimraf@5.0.10:
|
rimraf@5.0.10:
|
||||||
@@ -5432,6 +5743,8 @@ snapshots:
|
|||||||
|
|
||||||
tslog@4.10.2: {}
|
tslog@4.10.2: {}
|
||||||
|
|
||||||
|
tsscmp@1.0.6: {}
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.2
|
esbuild: 0.27.2
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { createNodesTool } from "./tools/nodes-tool.js";
|
|||||||
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
|
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
|
||||||
import { createSessionsListTool } from "./tools/sessions-list-tool.js";
|
import { createSessionsListTool } from "./tools/sessions-list-tool.js";
|
||||||
import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
|
import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
|
||||||
|
import { createSlackTool } from "./tools/slack-tool.js";
|
||||||
|
|
||||||
export function createClawdisTools(options?: {
|
export function createClawdisTools(options?: {
|
||||||
browserControlUrl?: string;
|
browserControlUrl?: string;
|
||||||
@@ -20,6 +21,7 @@ export function createClawdisTools(options?: {
|
|||||||
createNodesTool(),
|
createNodesTool(),
|
||||||
createCronTool(),
|
createCronTool(),
|
||||||
createDiscordTool(),
|
createDiscordTool(),
|
||||||
|
createSlackTool(),
|
||||||
createGatewayTool(),
|
createGatewayTool(),
|
||||||
createSessionsListTool(),
|
createSessionsListTool(),
|
||||||
createSessionsHistoryTool(),
|
createSessionsHistoryTool(),
|
||||||
|
|||||||
@@ -88,6 +88,14 @@ describe("createClawdisCodingTools", () => {
|
|||||||
expect(discord.some((tool) => tool.name === "discord")).toBe(true);
|
expect(discord.some((tool) => tool.name === "discord")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("scopes slack tool to slack surface", () => {
|
||||||
|
const other = createClawdisCodingTools({ surface: "whatsapp" });
|
||||||
|
expect(other.some((tool) => tool.name === "slack")).toBe(false);
|
||||||
|
|
||||||
|
const slack = createClawdisCodingTools({ surface: "slack" });
|
||||||
|
expect(slack.some((tool) => tool.name === "slack")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps read tool image metadata intact", async () => {
|
it("keeps read tool image metadata intact", async () => {
|
||||||
const tools = createClawdisCodingTools();
|
const tools = createClawdisCodingTools();
|
||||||
const readTool = tools.find((tool) => tool.name === "read");
|
const readTool = tools.find((tool) => tool.name === "read");
|
||||||
|
|||||||
@@ -441,6 +441,12 @@ function shouldIncludeDiscordTool(surface?: string): boolean {
|
|||||||
return normalized === "discord" || normalized.startsWith("discord:");
|
return normalized === "discord" || normalized.startsWith("discord:");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldIncludeSlackTool(surface?: string): boolean {
|
||||||
|
const normalized = normalizeSurface(surface);
|
||||||
|
if (!normalized) return false;
|
||||||
|
return normalized === "slack" || normalized.startsWith("slack:");
|
||||||
|
}
|
||||||
|
|
||||||
export function createClawdisCodingTools(options?: {
|
export function createClawdisCodingTools(options?: {
|
||||||
bash?: BashToolDefaults & ProcessToolDefaults;
|
bash?: BashToolDefaults & ProcessToolDefaults;
|
||||||
surface?: string;
|
surface?: string;
|
||||||
@@ -494,9 +500,12 @@ export function createClawdisCodingTools(options?: {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const allowDiscord = shouldIncludeDiscordTool(options?.surface);
|
const allowDiscord = shouldIncludeDiscordTool(options?.surface);
|
||||||
const filtered = allowDiscord
|
const allowSlack = shouldIncludeSlackTool(options?.surface);
|
||||||
? tools
|
const filtered = tools.filter((tool) => {
|
||||||
: tools.filter((tool) => tool.name !== "discord");
|
if (tool.name === "discord") return allowDiscord;
|
||||||
|
if (tool.name === "slack") return allowSlack;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
const sandboxed = sandbox
|
const sandboxed = sandbox
|
||||||
? filterToolsByPolicy(filtered, sandbox.tools)
|
? filterToolsByPolicy(filtered, sandbox.tools)
|
||||||
: filtered;
|
: filtered;
|
||||||
|
|||||||
@@ -66,10 +66,8 @@ export async function handleSlackAction(
|
|||||||
const to = readStringParam(params, "to", { required: true });
|
const to = readStringParam(params, "to", { required: true });
|
||||||
const content = readStringParam(params, "content", { required: true });
|
const content = readStringParam(params, "content", { required: true });
|
||||||
const mediaUrl = readStringParam(params, "mediaUrl");
|
const mediaUrl = readStringParam(params, "mediaUrl");
|
||||||
const replyTo = readStringParam(params, "replyTo");
|
|
||||||
const result = await sendSlackMessage(to, content, {
|
const result = await sendSlackMessage(to, content, {
|
||||||
mediaUrl: mediaUrl ?? undefined,
|
mediaUrl: mediaUrl ?? undefined,
|
||||||
replyTo: replyTo ?? undefined,
|
|
||||||
});
|
});
|
||||||
return jsonResult({ ok: true, result });
|
return jsonResult({ ok: true, result });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export const SlackToolSchema = Type.Union([
|
|||||||
to: Type.String(),
|
to: Type.String(),
|
||||||
content: Type.String(),
|
content: Type.String(),
|
||||||
mediaUrl: Type.Optional(Type.String()),
|
mediaUrl: Type.Optional(Type.String()),
|
||||||
replyTo: Type.Optional(Type.String()),
|
|
||||||
}),
|
}),
|
||||||
Type.Object({
|
Type.Object({
|
||||||
action: Type.Literal("editMessage"),
|
action: Type.Literal("editMessage"),
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ describe("resolveTextChunkLimit", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses the matching provider override", () => {
|
it("uses the matching provider override", () => {
|
||||||
const cfg = { discord: { textChunkLimit: 111 }, slack: { textChunkLimit: 222 } };
|
const cfg = {
|
||||||
|
discord: { textChunkLimit: 111 },
|
||||||
|
slack: { textChunkLimit: 222 },
|
||||||
|
};
|
||||||
expect(resolveTextChunkLimit(cfg, "discord")).toBe(111);
|
expect(resolveTextChunkLimit(cfg, "discord")).toBe(111);
|
||||||
expect(resolveTextChunkLimit(cfg, "slack")).toBe(222);
|
expect(resolveTextChunkLimit(cfg, "slack")).toBe(222);
|
||||||
expect(resolveTextChunkLimit(cfg, "telegram")).toBe(4000);
|
expect(resolveTextChunkLimit(cfg, "telegram")).toBe(4000);
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { sendMessageDiscord } from "../discord/send.js";
|
|||||||
import { sendMessageIMessage } from "../imessage/send.js";
|
import { sendMessageIMessage } from "../imessage/send.js";
|
||||||
import { logWebSelfId, sendMessageWhatsApp } from "../providers/web/index.js";
|
import { logWebSelfId, sendMessageWhatsApp } from "../providers/web/index.js";
|
||||||
import { sendMessageSignal } from "../signal/send.js";
|
import { sendMessageSignal } from "../signal/send.js";
|
||||||
|
import { sendMessageSlack } from "../slack/send.js";
|
||||||
import { sendMessageTelegram } from "../telegram/send.js";
|
import { sendMessageTelegram } from "../telegram/send.js";
|
||||||
|
|
||||||
export type CliDeps = {
|
export type CliDeps = {
|
||||||
sendMessageWhatsApp: typeof sendMessageWhatsApp;
|
sendMessageWhatsApp: typeof sendMessageWhatsApp;
|
||||||
sendMessageTelegram: typeof sendMessageTelegram;
|
sendMessageTelegram: typeof sendMessageTelegram;
|
||||||
sendMessageDiscord: typeof sendMessageDiscord;
|
sendMessageDiscord: typeof sendMessageDiscord;
|
||||||
|
sendMessageSlack: typeof sendMessageSlack;
|
||||||
sendMessageSignal: typeof sendMessageSignal;
|
sendMessageSignal: typeof sendMessageSignal;
|
||||||
sendMessageIMessage: typeof sendMessageIMessage;
|
sendMessageIMessage: typeof sendMessageIMessage;
|
||||||
};
|
};
|
||||||
@@ -17,6 +19,7 @@ export function createDefaultDeps(): CliDeps {
|
|||||||
sendMessageWhatsApp,
|
sendMessageWhatsApp,
|
||||||
sendMessageTelegram,
|
sendMessageTelegram,
|
||||||
sendMessageDiscord,
|
sendMessageDiscord,
|
||||||
|
sendMessageSlack,
|
||||||
sendMessageSignal,
|
sendMessageSignal,
|
||||||
sendMessageIMessage,
|
sendMessageIMessage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export function buildProgram() {
|
|||||||
program
|
program
|
||||||
.command("send")
|
.command("send")
|
||||||
.description(
|
.description(
|
||||||
"Send a message (WhatsApp Web, Telegram bot, Discord, Signal, iMessage)",
|
"Send a message (WhatsApp Web, Telegram bot, Discord, Slack, Signal, iMessage)",
|
||||||
)
|
)
|
||||||
.requiredOption(
|
.requiredOption(
|
||||||
"-t, --to <number>",
|
"-t, --to <number>",
|
||||||
@@ -318,7 +318,7 @@ export function buildProgram() {
|
|||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
"--provider <provider>",
|
"--provider <provider>",
|
||||||
"Delivery provider: whatsapp|telegram|discord|signal|imessage (default: whatsapp)",
|
"Delivery provider: whatsapp|telegram|discord|slack|signal|imessage (default: whatsapp)",
|
||||||
)
|
)
|
||||||
.option("--dry-run", "Print payload and skip sending", false)
|
.option("--dry-run", "Print payload and skip sending", false)
|
||||||
.option("--json", "Output result as JSON", false)
|
.option("--json", "Output result as JSON", false)
|
||||||
@@ -361,7 +361,7 @@ Examples:
|
|||||||
.option("--verbose <on|off>", "Persist agent verbose level for the session")
|
.option("--verbose <on|off>", "Persist agent verbose level for the session")
|
||||||
.option(
|
.option(
|
||||||
"--provider <provider>",
|
"--provider <provider>",
|
||||||
"Delivery provider: whatsapp|telegram|discord|signal|imessage (default: whatsapp)",
|
"Delivery provider: whatsapp|telegram|discord|slack|signal|imessage (default: whatsapp)",
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
"--deliver",
|
"--deliver",
|
||||||
@@ -411,7 +411,7 @@ Examples:
|
|||||||
.option("--json", "Output JSON instead of text", false)
|
.option("--json", "Output JSON instead of text", false)
|
||||||
.option(
|
.option(
|
||||||
"--deep",
|
"--deep",
|
||||||
"Probe providers (WhatsApp Web + Telegram + Discord + Signal)",
|
"Probe providers (WhatsApp Web + Telegram + Discord + Slack + Signal)",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.option("--timeout <ms>", "Probe timeout in milliseconds", "10000")
|
.option("--timeout <ms>", "Probe timeout in milliseconds", "10000")
|
||||||
@@ -422,7 +422,7 @@ Examples:
|
|||||||
Examples:
|
Examples:
|
||||||
clawdis status # show linked account + session store summary
|
clawdis status # show linked account + session store summary
|
||||||
clawdis status --json # machine-readable output
|
clawdis status --json # machine-readable output
|
||||||
clawdis status --deep # run provider probes (WA + Telegram + Discord + Signal)
|
clawdis status --deep # run provider probes (WA + Telegram + Discord + Slack + Signal)
|
||||||
clawdis status --deep --timeout 5000 # tighten probe timeout`,
|
clawdis status --deep --timeout 5000 # tighten probe timeout`,
|
||||||
)
|
)
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ export async function agentCommand(
|
|||||||
const whatsappTarget = opts.to ? normalizeE164(opts.to) : allowFrom[0];
|
const whatsappTarget = opts.to ? normalizeE164(opts.to) : allowFrom[0];
|
||||||
const telegramTarget = opts.to?.trim() || undefined;
|
const telegramTarget = opts.to?.trim() || undefined;
|
||||||
const discordTarget = opts.to?.trim() || undefined;
|
const discordTarget = opts.to?.trim() || undefined;
|
||||||
|
const slackTarget = opts.to?.trim() || undefined;
|
||||||
const signalTarget = opts.to?.trim() || undefined;
|
const signalTarget = opts.to?.trim() || undefined;
|
||||||
const imessageTarget = opts.to?.trim() || undefined;
|
const imessageTarget = opts.to?.trim() || undefined;
|
||||||
|
|
||||||
@@ -484,11 +485,13 @@ export async function agentCommand(
|
|||||||
? whatsappTarget
|
? whatsappTarget
|
||||||
: deliveryProvider === "discord"
|
: deliveryProvider === "discord"
|
||||||
? discordTarget
|
? discordTarget
|
||||||
: deliveryProvider === "signal"
|
: deliveryProvider === "slack"
|
||||||
? signalTarget
|
? slackTarget
|
||||||
: deliveryProvider === "imessage"
|
: deliveryProvider === "signal"
|
||||||
? imessageTarget
|
? signalTarget
|
||||||
: undefined;
|
: deliveryProvider === "imessage"
|
||||||
|
? imessageTarget
|
||||||
|
: undefined;
|
||||||
const message = `Delivery failed (${deliveryProvider}${deliveryTarget ? ` to ${deliveryTarget}` : ""}): ${String(err)}`;
|
const message = `Delivery failed (${deliveryProvider}${deliveryTarget ? ` to ${deliveryTarget}` : ""}): ${String(err)}`;
|
||||||
runtime.error?.(message);
|
runtime.error?.(message);
|
||||||
if (!runtime.error) runtime.log(message);
|
if (!runtime.error) runtime.log(message);
|
||||||
@@ -514,6 +517,13 @@ export async function agentCommand(
|
|||||||
if (!bestEffortDeliver) throw err;
|
if (!bestEffortDeliver) throw err;
|
||||||
logDeliveryError(err);
|
logDeliveryError(err);
|
||||||
}
|
}
|
||||||
|
if (deliveryProvider === "slack" && !slackTarget) {
|
||||||
|
const err = new Error(
|
||||||
|
"Delivering to Slack requires --to <channelId|user:ID|channel:ID>",
|
||||||
|
);
|
||||||
|
if (!bestEffortDeliver) throw err;
|
||||||
|
logDeliveryError(err);
|
||||||
|
}
|
||||||
if (deliveryProvider === "signal" && !signalTarget) {
|
if (deliveryProvider === "signal" && !signalTarget) {
|
||||||
const err = new Error(
|
const err = new Error(
|
||||||
"Delivering to Signal requires --to <E.164|group:ID|signal:group:ID|signal:+E.164>",
|
"Delivering to Signal requires --to <E.164|group:ID|signal:group:ID|signal:+E.164>",
|
||||||
@@ -539,6 +549,7 @@ export async function agentCommand(
|
|||||||
deliveryProvider !== "whatsapp" &&
|
deliveryProvider !== "whatsapp" &&
|
||||||
deliveryProvider !== "telegram" &&
|
deliveryProvider !== "telegram" &&
|
||||||
deliveryProvider !== "discord" &&
|
deliveryProvider !== "discord" &&
|
||||||
|
deliveryProvider !== "slack" &&
|
||||||
deliveryProvider !== "signal" &&
|
deliveryProvider !== "signal" &&
|
||||||
deliveryProvider !== "imessage" &&
|
deliveryProvider !== "imessage" &&
|
||||||
deliveryProvider !== "webchat"
|
deliveryProvider !== "webchat"
|
||||||
@@ -574,6 +585,7 @@ export async function agentCommand(
|
|||||||
deliveryProvider === "whatsapp" ||
|
deliveryProvider === "whatsapp" ||
|
||||||
deliveryProvider === "telegram" ||
|
deliveryProvider === "telegram" ||
|
||||||
deliveryProvider === "discord" ||
|
deliveryProvider === "discord" ||
|
||||||
|
deliveryProvider === "slack" ||
|
||||||
deliveryProvider === "signal" ||
|
deliveryProvider === "signal" ||
|
||||||
deliveryProvider === "imessage"
|
deliveryProvider === "imessage"
|
||||||
? resolveTextChunkLimit(cfg, deliveryProvider)
|
? resolveTextChunkLimit(cfg, deliveryProvider)
|
||||||
@@ -666,6 +678,26 @@ export async function agentCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deliveryProvider === "slack" && slackTarget) {
|
||||||
|
try {
|
||||||
|
if (media.length === 0) {
|
||||||
|
await deps.sendMessageSlack(slackTarget, text);
|
||||||
|
} else {
|
||||||
|
let first = true;
|
||||||
|
for (const url of media) {
|
||||||
|
const caption = first ? text : "";
|
||||||
|
first = false;
|
||||||
|
await deps.sendMessageSlack(slackTarget, caption, {
|
||||||
|
mediaUrl: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!bestEffortDeliver) throw err;
|
||||||
|
logDeliveryError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (deliveryProvider === "signal" && signalTarget) {
|
if (deliveryProvider === "signal" && signalTarget) {
|
||||||
try {
|
try {
|
||||||
if (media.length === 0) {
|
if (media.length === 0) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ async function noteProviderPrimer(prompter: WizardPrompter): Promise<void> {
|
|||||||
"WhatsApp: dedicated second number recommended; primary number OK (self-chat).",
|
"WhatsApp: dedicated second number recommended; primary number OK (self-chat).",
|
||||||
"Telegram: Bot API (token from @BotFather), replies via your bot.",
|
"Telegram: Bot API (token from @BotFather), replies via your bot.",
|
||||||
"Discord: Bot token from Discord Developer Portal; invite bot to your server.",
|
"Discord: Bot token from Discord Developer Portal; invite bot to your server.",
|
||||||
|
"Slack: Socket Mode app token + bot token, DMs via App Home Messages tab.",
|
||||||
"Signal: signal-cli as a linked device; separate number recommended.",
|
"Signal: signal-cli as a linked device; separate number recommended.",
|
||||||
"iMessage: local imsg CLI; separate Apple ID recommended only on a separate Mac.",
|
"iMessage: local imsg CLI; separate Apple ID recommended only on a separate Mac.",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
@@ -74,6 +75,10 @@ function buildSlackManifest(botName: string) {
|
|||||||
display_name: safeName,
|
display_name: safeName,
|
||||||
always_online: false,
|
always_online: false,
|
||||||
},
|
},
|
||||||
|
app_home: {
|
||||||
|
messages_tab_enabled: true,
|
||||||
|
messages_tab_read_only_enabled: false,
|
||||||
|
},
|
||||||
slash_commands: [
|
slash_commands: [
|
||||||
{
|
{
|
||||||
command: "/clawd",
|
command: "/clawd",
|
||||||
@@ -94,6 +99,7 @@ function buildSlackManifest(botName: string) {
|
|||||||
"users:read",
|
"users:read",
|
||||||
"app_mentions:read",
|
"app_mentions:read",
|
||||||
"reactions:read",
|
"reactions:read",
|
||||||
|
"reactions:write",
|
||||||
"pins:read",
|
"pins:read",
|
||||||
"pins:write",
|
"pins:write",
|
||||||
"emoji:read",
|
"emoji:read",
|
||||||
@@ -137,6 +143,7 @@ async function noteSlackTokenHelp(
|
|||||||
"2) Add Socket Mode + enable it to get the app-level token (xapp-...)",
|
"2) Add Socket Mode + enable it to get the app-level token (xapp-...)",
|
||||||
"3) OAuth & Permissions → install app to workspace (xoxb- bot token)",
|
"3) OAuth & Permissions → install app to workspace (xoxb- bot token)",
|
||||||
"4) Enable Event Subscriptions (socket) for message events",
|
"4) Enable Event Subscriptions (socket) for message events",
|
||||||
|
"5) App Home → enable the Messages tab for DMs",
|
||||||
"Tip: set SLACK_BOT_TOKEN + SLACK_APP_TOKEN in your env.",
|
"Tip: set SLACK_BOT_TOKEN + SLACK_APP_TOKEN in your env.",
|
||||||
"",
|
"",
|
||||||
"Manifest (JSON):",
|
"Manifest (JSON):",
|
||||||
@@ -237,10 +244,16 @@ export async function setupProviders(
|
|||||||
const whatsappLinked = await detectWhatsAppLinked();
|
const whatsappLinked = await detectWhatsAppLinked();
|
||||||
const telegramEnv = Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
|
const telegramEnv = Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
|
||||||
const discordEnv = Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
|
const discordEnv = Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
|
||||||
|
const slackBotEnv = Boolean(process.env.SLACK_BOT_TOKEN?.trim());
|
||||||
|
const slackAppEnv = Boolean(process.env.SLACK_APP_TOKEN?.trim());
|
||||||
const telegramConfigured = Boolean(
|
const telegramConfigured = Boolean(
|
||||||
telegramEnv || cfg.telegram?.botToken || cfg.telegram?.tokenFile,
|
telegramEnv || cfg.telegram?.botToken || cfg.telegram?.tokenFile,
|
||||||
);
|
);
|
||||||
const discordConfigured = Boolean(discordEnv || cfg.discord?.token);
|
const discordConfigured = Boolean(discordEnv || cfg.discord?.token);
|
||||||
|
const slackConfigured = Boolean(
|
||||||
|
(slackBotEnv && slackAppEnv) ||
|
||||||
|
(cfg.slack?.botToken && cfg.slack?.appToken),
|
||||||
|
);
|
||||||
const signalConfigured = Boolean(
|
const signalConfigured = Boolean(
|
||||||
cfg.signal?.account || cfg.signal?.httpUrl || cfg.signal?.httpPort,
|
cfg.signal?.account || cfg.signal?.httpUrl || cfg.signal?.httpPort,
|
||||||
);
|
);
|
||||||
@@ -257,6 +270,7 @@ export async function setupProviders(
|
|||||||
`WhatsApp: ${whatsappLinked ? "linked" : "not linked"}`,
|
`WhatsApp: ${whatsappLinked ? "linked" : "not linked"}`,
|
||||||
`Telegram: ${telegramConfigured ? "configured" : "needs token"}`,
|
`Telegram: ${telegramConfigured ? "configured" : "needs token"}`,
|
||||||
`Discord: ${discordConfigured ? "configured" : "needs token"}`,
|
`Discord: ${discordConfigured ? "configured" : "needs token"}`,
|
||||||
|
`Slack: ${slackConfigured ? "configured" : "needs tokens"}`,
|
||||||
`Signal: ${signalConfigured ? "configured" : "needs setup"}`,
|
`Signal: ${signalConfigured ? "configured" : "needs setup"}`,
|
||||||
`iMessage: ${imessageConfigured ? "configured" : "needs setup"}`,
|
`iMessage: ${imessageConfigured ? "configured" : "needs setup"}`,
|
||||||
`signal-cli: ${signalCliDetected ? "found" : "missing"} (${signalCliPath})`,
|
`signal-cli: ${signalCliDetected ? "found" : "missing"} (${signalCliPath})`,
|
||||||
@@ -291,6 +305,11 @@ export async function setupProviders(
|
|||||||
label: "Discord (Bot API)",
|
label: "Discord (Bot API)",
|
||||||
hint: discordConfigured ? "configured" : "needs token",
|
hint: discordConfigured ? "configured" : "needs token",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "slack",
|
||||||
|
label: "Slack (Socket Mode)",
|
||||||
|
hint: slackConfigured ? "configured" : "needs tokens",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: "signal",
|
value: "signal",
|
||||||
label: "Signal (signal-cli)",
|
label: "Signal (signal-cli)",
|
||||||
@@ -695,6 +714,19 @@ export async function setupProviders(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!selection.includes("slack") && slackConfigured) {
|
||||||
|
const disable = await prompter.confirm({
|
||||||
|
message: "Disable Slack provider?",
|
||||||
|
initialValue: false,
|
||||||
|
});
|
||||||
|
if (disable) {
|
||||||
|
next = {
|
||||||
|
...next,
|
||||||
|
slack: { ...next.slack, enabled: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!selection.includes("signal") && signalConfigured) {
|
if (!selection.includes("signal") && signalConfigured) {
|
||||||
const disable = await prompter.confirm({
|
const disable = await prompter.confirm({
|
||||||
message: "Disable Signal provider?",
|
message: "Disable Signal provider?",
|
||||||
@@ -724,4 +756,3 @@ export async function setupProviders(
|
|||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type ProviderChoice =
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const makeDeps = (overrides: Partial<CliDeps> = {}): CliDeps => ({
|
|||||||
sendMessageWhatsApp: vi.fn(),
|
sendMessageWhatsApp: vi.fn(),
|
||||||
sendMessageTelegram: vi.fn(),
|
sendMessageTelegram: vi.fn(),
|
||||||
sendMessageDiscord: vi.fn(),
|
sendMessageDiscord: vi.fn(),
|
||||||
|
sendMessageSlack: vi.fn(),
|
||||||
sendMessageSignal: vi.fn(),
|
sendMessageSignal: vi.fn(),
|
||||||
sendMessageIMessage: vi.fn(),
|
sendMessageIMessage: vi.fn(),
|
||||||
...overrides,
|
...overrides,
|
||||||
@@ -173,6 +174,25 @@ describe("sendCommand", () => {
|
|||||||
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
|
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("routes to slack provider", async () => {
|
||||||
|
const deps = makeDeps({
|
||||||
|
sendMessageSlack: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ messageId: "s1", channelId: "C123" }),
|
||||||
|
});
|
||||||
|
await sendCommand(
|
||||||
|
{ to: "channel:C123", message: "hi", provider: "slack" },
|
||||||
|
deps,
|
||||||
|
runtime,
|
||||||
|
);
|
||||||
|
expect(deps.sendMessageSlack).toHaveBeenCalledWith(
|
||||||
|
"channel:C123",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ mediaUrl: undefined }),
|
||||||
|
);
|
||||||
|
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("routes to imessage provider", async () => {
|
it("routes to imessage provider", async () => {
|
||||||
const deps = makeDeps({
|
const deps = makeDeps({
|
||||||
sendMessageIMessage: vi.fn().mockResolvedValue({ messageId: "i1" }),
|
sendMessageIMessage: vi.fn().mockResolvedValue({ messageId: "i1" }),
|
||||||
|
|||||||
@@ -86,6 +86,34 @@ export async function sendCommand(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider === "slack") {
|
||||||
|
const result = await deps.sendMessageSlack(opts.to, opts.message, {
|
||||||
|
mediaUrl: opts.media,
|
||||||
|
});
|
||||||
|
runtime.log(
|
||||||
|
success(
|
||||||
|
`✅ Sent via slack. Message ID: ${result.messageId} (channel ${result.channelId})`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (opts.json) {
|
||||||
|
runtime.log(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
provider: "slack",
|
||||||
|
via: "direct",
|
||||||
|
to: opts.to,
|
||||||
|
channelId: result.channelId,
|
||||||
|
messageId: result.messageId,
|
||||||
|
mediaUrl: opts.media ?? null,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (provider === "signal") {
|
if (provider === "signal") {
|
||||||
const result = await deps.sendMessageSignal(opts.to, opts.message, {
|
const result = await deps.sendMessageSignal(opts.to, opts.message, {
|
||||||
mediaUrl: opts.media,
|
mediaUrl: opts.media,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export type SessionEntry = {
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage"
|
| "imessage"
|
||||||
| "webchat";
|
| "webchat";
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export type AgentElevatedAllowFromConfig = {
|
|||||||
whatsapp?: string[];
|
whatsapp?: string[];
|
||||||
telegram?: Array<string | number>;
|
telegram?: Array<string | number>;
|
||||||
discord?: Array<string | number>;
|
discord?: Array<string | number>;
|
||||||
|
slack?: Array<string | number>;
|
||||||
signal?: Array<string | number>;
|
signal?: Array<string | number>;
|
||||||
imessage?: Array<string | number>;
|
imessage?: Array<string | number>;
|
||||||
webchat?: Array<string | number>;
|
webchat?: Array<string | number>;
|
||||||
@@ -337,7 +338,6 @@ export type SlackConfig = {
|
|||||||
botToken?: string;
|
botToken?: string;
|
||||||
appToken?: string;
|
appToken?: string;
|
||||||
textChunkLimit?: number;
|
textChunkLimit?: number;
|
||||||
replyToMode?: ReplyToMode;
|
|
||||||
mediaMaxMb?: number;
|
mediaMaxMb?: number;
|
||||||
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
||||||
reactionNotifications?: SlackReactionNotificationMode;
|
reactionNotifications?: SlackReactionNotificationMode;
|
||||||
|
|||||||
@@ -417,6 +417,7 @@ export const ClawdisSchema = z.object({
|
|||||||
whatsapp: z.array(z.string()).optional(),
|
whatsapp: z.array(z.string()).optional(),
|
||||||
telegram: z.array(z.union([z.string(), z.number()])).optional(),
|
telegram: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
discord: z.array(z.union([z.string(), z.number()])).optional(),
|
discord: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
|
slack: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
signal: 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(),
|
imessage: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
webchat: z.array(z.union([z.string(), z.number()])).optional(),
|
webchat: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
@@ -628,7 +629,6 @@ export const ClawdisSchema = z.object({
|
|||||||
botToken: z.string().optional(),
|
botToken: z.string().optional(),
|
||||||
appToken: z.string().optional(),
|
appToken: z.string().optional(),
|
||||||
textChunkLimit: z.number().int().positive().optional(),
|
textChunkLimit: z.number().int().positive().optional(),
|
||||||
replyToMode: ReplyToModeSchema.optional(),
|
|
||||||
mediaMaxMb: z.number().positive().optional(),
|
mediaMaxMb: z.number().positive().optional(),
|
||||||
reactionNotifications: z
|
reactionNotifications: z
|
||||||
.enum(["off", "own", "all", "allowlist"])
|
.enum(["off", "own", "all", "allowlist"])
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function resolveDeliveryTarget(
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
@@ -92,6 +93,7 @@ function resolveDeliveryTarget(
|
|||||||
requestedChannel === "whatsapp" ||
|
requestedChannel === "whatsapp" ||
|
||||||
requestedChannel === "telegram" ||
|
requestedChannel === "telegram" ||
|
||||||
requestedChannel === "discord" ||
|
requestedChannel === "discord" ||
|
||||||
|
requestedChannel === "slack" ||
|
||||||
requestedChannel === "signal" ||
|
requestedChannel === "signal" ||
|
||||||
requestedChannel === "imessage"
|
requestedChannel === "imessage"
|
||||||
) {
|
) {
|
||||||
@@ -447,6 +449,43 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
return { status: "error", summary, error: String(err) };
|
return { status: "error", summary, error: String(err) };
|
||||||
return { status: "ok", summary };
|
return { status: "ok", summary };
|
||||||
}
|
}
|
||||||
|
} else if (resolvedDelivery.channel === "slack") {
|
||||||
|
if (!resolvedDelivery.to) {
|
||||||
|
if (!bestEffortDeliver)
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
summary,
|
||||||
|
error:
|
||||||
|
"Cron delivery to Slack requires --channel slack and --to <channelId|user:ID>",
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
status: "skipped",
|
||||||
|
summary: "Delivery skipped (no Slack destination).",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const slackTarget = resolvedDelivery.to;
|
||||||
|
try {
|
||||||
|
for (const payload of payloads) {
|
||||||
|
const mediaList =
|
||||||
|
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||||
|
if (mediaList.length === 0) {
|
||||||
|
await params.deps.sendMessageSlack(slackTarget, payload.text ?? "");
|
||||||
|
} else {
|
||||||
|
let first = true;
|
||||||
|
for (const url of mediaList) {
|
||||||
|
const caption = first ? (payload.text ?? "") : "";
|
||||||
|
first = false;
|
||||||
|
await params.deps.sendMessageSlack(slackTarget, caption, {
|
||||||
|
mediaUrl: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!bestEffortDeliver)
|
||||||
|
return { status: "error", summary, error: String(err) };
|
||||||
|
return { status: "ok", summary };
|
||||||
|
}
|
||||||
} else if (resolvedDelivery.channel === "signal") {
|
} else if (resolvedDelivery.channel === "signal") {
|
||||||
if (!resolvedDelivery.to) {
|
if (!resolvedDelivery.to) {
|
||||||
if (!bestEffortDeliver)
|
if (!bestEffortDeliver)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type CronPayload =
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type ProviderKind =
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ type ReloadAction =
|
|||||||
| "restart-provider:whatsapp"
|
| "restart-provider:whatsapp"
|
||||||
| "restart-provider:telegram"
|
| "restart-provider:telegram"
|
||||||
| "restart-provider:discord"
|
| "restart-provider:discord"
|
||||||
|
| "restart-provider:slack"
|
||||||
| "restart-provider:signal"
|
| "restart-provider:signal"
|
||||||
| "restart-provider:imessage";
|
| "restart-provider:imessage";
|
||||||
|
|
||||||
@@ -70,6 +72,7 @@ const RELOAD_RULES: ReloadRule[] = [
|
|||||||
{ prefix: "web", kind: "hot", actions: ["restart-provider:whatsapp"] },
|
{ prefix: "web", kind: "hot", actions: ["restart-provider:whatsapp"] },
|
||||||
{ prefix: "telegram", kind: "hot", actions: ["restart-provider:telegram"] },
|
{ prefix: "telegram", kind: "hot", actions: ["restart-provider:telegram"] },
|
||||||
{ prefix: "discord", kind: "hot", actions: ["restart-provider:discord"] },
|
{ prefix: "discord", kind: "hot", actions: ["restart-provider:discord"] },
|
||||||
|
{ prefix: "slack", kind: "hot", actions: ["restart-provider:slack"] },
|
||||||
{ prefix: "signal", kind: "hot", actions: ["restart-provider:signal"] },
|
{ prefix: "signal", kind: "hot", actions: ["restart-provider:signal"] },
|
||||||
{ prefix: "imessage", kind: "hot", actions: ["restart-provider:imessage"] },
|
{ prefix: "imessage", kind: "hot", actions: ["restart-provider:imessage"] },
|
||||||
{ prefix: "identity", kind: "none" },
|
{ prefix: "identity", kind: "none" },
|
||||||
@@ -200,6 +203,9 @@ export function buildGatewayReloadPlan(
|
|||||||
case "restart-provider:discord":
|
case "restart-provider:discord":
|
||||||
plan.restartProviders.add("discord");
|
plan.restartProviders.add("discord");
|
||||||
break;
|
break;
|
||||||
|
case "restart-provider:slack":
|
||||||
|
plan.restartProviders.add("slack");
|
||||||
|
break;
|
||||||
case "restart-provider:signal":
|
case "restart-provider:signal":
|
||||||
plan.restartProviders.add("signal");
|
plan.restartProviders.add("signal");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type HookMappingResolved = {
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
@@ -61,6 +62,7 @@ export type HookAction =
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
@@ -99,7 +101,14 @@ type HookTransformResult = Partial<{
|
|||||||
name: string;
|
name: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
deliver: boolean;
|
deliver: boolean;
|
||||||
channel: "last" | "whatsapp" | "telegram" | "discord" | "signal" | "imessage";
|
channel:
|
||||||
|
| "last"
|
||||||
|
| "whatsapp"
|
||||||
|
| "telegram"
|
||||||
|
| "discord"
|
||||||
|
| "slack"
|
||||||
|
| "signal"
|
||||||
|
| "imessage";
|
||||||
to: string;
|
to: string;
|
||||||
thinking: string;
|
thinking: string;
|
||||||
timeoutSeconds: number;
|
timeoutSeconds: number;
|
||||||
|
|||||||
@@ -635,6 +635,7 @@ export const CronPayloadSchema = Type.Union([
|
|||||||
Type.Literal("whatsapp"),
|
Type.Literal("whatsapp"),
|
||||||
Type.Literal("telegram"),
|
Type.Literal("telegram"),
|
||||||
Type.Literal("discord"),
|
Type.Literal("discord"),
|
||||||
|
Type.Literal("slack"),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
to: Type.Optional(Type.String()),
|
to: Type.Optional(Type.String()),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type HookDispatchers = {
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import { type DiscordProbe, probeDiscord } from "../../discord/probe.js";
|
|||||||
import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js";
|
import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js";
|
||||||
import { webAuthExists } from "../../providers/web/index.js";
|
import { webAuthExists } from "../../providers/web/index.js";
|
||||||
import { probeSignal, type SignalProbe } from "../../signal/probe.js";
|
import { probeSignal, type SignalProbe } from "../../signal/probe.js";
|
||||||
|
import { probeSlack, type SlackProbe } from "../../slack/probe.js";
|
||||||
|
import {
|
||||||
|
resolveSlackAppToken,
|
||||||
|
resolveSlackBotToken,
|
||||||
|
} from "../../slack/token.js";
|
||||||
import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js";
|
import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js";
|
||||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||||
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
|
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
|
||||||
@@ -74,6 +79,41 @@ export const providersHandlers: GatewayRequestHandlers = {
|
|||||||
discordLastProbeAt = Date.now();
|
discordLastProbeAt = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slackCfg = cfg.slack;
|
||||||
|
const slackEnabled = slackCfg?.enabled !== false;
|
||||||
|
const slackBotEnvToken = slackEnabled
|
||||||
|
? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN)
|
||||||
|
: undefined;
|
||||||
|
const slackBotConfigToken = slackEnabled
|
||||||
|
? resolveSlackBotToken(slackCfg?.botToken)
|
||||||
|
: undefined;
|
||||||
|
const slackBotToken = slackBotEnvToken ?? slackBotConfigToken ?? "";
|
||||||
|
const slackBotTokenSource = slackBotEnvToken
|
||||||
|
? "env"
|
||||||
|
: slackBotConfigToken
|
||||||
|
? "config"
|
||||||
|
: "none";
|
||||||
|
const slackAppEnvToken = slackEnabled
|
||||||
|
? resolveSlackAppToken(process.env.SLACK_APP_TOKEN)
|
||||||
|
: undefined;
|
||||||
|
const slackAppConfigToken = slackEnabled
|
||||||
|
? resolveSlackAppToken(slackCfg?.appToken)
|
||||||
|
: undefined;
|
||||||
|
const slackAppToken = slackAppEnvToken ?? slackAppConfigToken ?? "";
|
||||||
|
const slackAppTokenSource = slackAppEnvToken
|
||||||
|
? "env"
|
||||||
|
: slackAppConfigToken
|
||||||
|
? "config"
|
||||||
|
: "none";
|
||||||
|
const slackConfigured =
|
||||||
|
slackEnabled && Boolean(slackBotToken) && Boolean(slackAppToken);
|
||||||
|
let slackProbe: SlackProbe | undefined;
|
||||||
|
let slackLastProbeAt: number | null = null;
|
||||||
|
if (probe && slackConfigured) {
|
||||||
|
slackProbe = await probeSlack(slackBotToken, timeoutMs);
|
||||||
|
slackLastProbeAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
const signalCfg = cfg.signal;
|
const signalCfg = cfg.signal;
|
||||||
const signalEnabled = signalCfg?.enabled !== false;
|
const signalEnabled = signalCfg?.enabled !== false;
|
||||||
const signalHost = signalCfg?.httpHost?.trim() || "127.0.0.1";
|
const signalHost = signalCfg?.httpHost?.trim() || "127.0.0.1";
|
||||||
@@ -152,6 +192,17 @@ export const providersHandlers: GatewayRequestHandlers = {
|
|||||||
probe: discordProbe,
|
probe: discordProbe,
|
||||||
lastProbeAt: discordLastProbeAt,
|
lastProbeAt: discordLastProbeAt,
|
||||||
},
|
},
|
||||||
|
slack: {
|
||||||
|
configured: slackConfigured,
|
||||||
|
botTokenSource: slackBotTokenSource,
|
||||||
|
appTokenSource: slackAppTokenSource,
|
||||||
|
running: runtime.slack.running,
|
||||||
|
lastStartAt: runtime.slack.lastStartAt ?? null,
|
||||||
|
lastStopAt: runtime.slack.lastStopAt ?? null,
|
||||||
|
lastError: runtime.slack.lastError ?? null,
|
||||||
|
probe: slackProbe,
|
||||||
|
lastProbeAt: slackLastProbeAt,
|
||||||
|
},
|
||||||
signal: {
|
signal: {
|
||||||
configured: signalConfigured,
|
configured: signalConfigured,
|
||||||
baseUrl: signalBaseUrl,
|
baseUrl: signalBaseUrl,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { sendMessageDiscord } from "../../discord/index.js";
|
|||||||
import { shouldLogVerbose } from "../../globals.js";
|
import { shouldLogVerbose } from "../../globals.js";
|
||||||
import { sendMessageIMessage } from "../../imessage/index.js";
|
import { sendMessageIMessage } from "../../imessage/index.js";
|
||||||
import { sendMessageSignal } from "../../signal/index.js";
|
import { sendMessageSignal } from "../../signal/index.js";
|
||||||
|
import { sendMessageSlack } from "../../slack/send.js";
|
||||||
import { sendMessageTelegram } from "../../telegram/send.js";
|
import { sendMessageTelegram } from "../../telegram/send.js";
|
||||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||||
import { sendMessageWhatsApp } from "../../web/outbound.js";
|
import { sendMessageWhatsApp } from "../../web/outbound.js";
|
||||||
@@ -87,6 +88,22 @@ export const sendHandlers: GatewayRequestHandlers = {
|
|||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
respond(true, payload, undefined, { provider });
|
respond(true, payload, undefined, { provider });
|
||||||
|
} else if (provider === "slack") {
|
||||||
|
const result = await sendMessageSlack(to, message, {
|
||||||
|
mediaUrl: request.mediaUrl,
|
||||||
|
});
|
||||||
|
const payload = {
|
||||||
|
runId: idem,
|
||||||
|
messageId: result.messageId,
|
||||||
|
channelId: result.channelId,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
context.dedupe.set(`send:${idem}`, {
|
||||||
|
ts: Date.now(),
|
||||||
|
ok: true,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
respond(true, payload, undefined, { provider });
|
||||||
} else if (provider === "signal") {
|
} else if (provider === "signal") {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const host = cfg.signal?.httpHost?.trim() || "127.0.0.1";
|
const host = cfg.signal?.httpHost?.trim() || "127.0.0.1";
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import type { createSubsystemLogger } from "../logging.js";
|
|||||||
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { monitorSignalProvider } from "../signal/index.js";
|
import { monitorSignalProvider } from "../signal/index.js";
|
||||||
|
import {
|
||||||
|
monitorSlackProvider,
|
||||||
|
resolveSlackAppToken,
|
||||||
|
resolveSlackBotToken,
|
||||||
|
} from "../slack/index.js";
|
||||||
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||||
import { probeTelegram } from "../telegram/probe.js";
|
import { probeTelegram } from "../telegram/probe.js";
|
||||||
import { resolveTelegramToken } from "../telegram/token.js";
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
@@ -29,6 +34,13 @@ export type DiscordRuntimeStatus = {
|
|||||||
lastError?: string | null;
|
lastError?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SlackRuntimeStatus = {
|
||||||
|
running: boolean;
|
||||||
|
lastStartAt?: number | null;
|
||||||
|
lastStopAt?: number | null;
|
||||||
|
lastError?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type SignalRuntimeStatus = {
|
export type SignalRuntimeStatus = {
|
||||||
running: boolean;
|
running: boolean;
|
||||||
lastStartAt?: number | null;
|
lastStartAt?: number | null;
|
||||||
@@ -50,6 +62,7 @@ export type ProviderRuntimeSnapshot = {
|
|||||||
whatsapp: WebProviderStatus;
|
whatsapp: WebProviderStatus;
|
||||||
telegram: TelegramRuntimeStatus;
|
telegram: TelegramRuntimeStatus;
|
||||||
discord: DiscordRuntimeStatus;
|
discord: DiscordRuntimeStatus;
|
||||||
|
slack: SlackRuntimeStatus;
|
||||||
signal: SignalRuntimeStatus;
|
signal: SignalRuntimeStatus;
|
||||||
imessage: IMessageRuntimeStatus;
|
imessage: IMessageRuntimeStatus;
|
||||||
};
|
};
|
||||||
@@ -61,11 +74,13 @@ type ProviderManagerOptions = {
|
|||||||
logWhatsApp: SubsystemLogger;
|
logWhatsApp: SubsystemLogger;
|
||||||
logTelegram: SubsystemLogger;
|
logTelegram: SubsystemLogger;
|
||||||
logDiscord: SubsystemLogger;
|
logDiscord: SubsystemLogger;
|
||||||
|
logSlack: SubsystemLogger;
|
||||||
logSignal: SubsystemLogger;
|
logSignal: SubsystemLogger;
|
||||||
logIMessage: SubsystemLogger;
|
logIMessage: SubsystemLogger;
|
||||||
whatsappRuntimeEnv: RuntimeEnv;
|
whatsappRuntimeEnv: RuntimeEnv;
|
||||||
telegramRuntimeEnv: RuntimeEnv;
|
telegramRuntimeEnv: RuntimeEnv;
|
||||||
discordRuntimeEnv: RuntimeEnv;
|
discordRuntimeEnv: RuntimeEnv;
|
||||||
|
slackRuntimeEnv: RuntimeEnv;
|
||||||
signalRuntimeEnv: RuntimeEnv;
|
signalRuntimeEnv: RuntimeEnv;
|
||||||
imessageRuntimeEnv: RuntimeEnv;
|
imessageRuntimeEnv: RuntimeEnv;
|
||||||
};
|
};
|
||||||
@@ -79,6 +94,8 @@ export type ProviderManager = {
|
|||||||
stopTelegramProvider: () => Promise<void>;
|
stopTelegramProvider: () => Promise<void>;
|
||||||
startDiscordProvider: () => Promise<void>;
|
startDiscordProvider: () => Promise<void>;
|
||||||
stopDiscordProvider: () => Promise<void>;
|
stopDiscordProvider: () => Promise<void>;
|
||||||
|
startSlackProvider: () => Promise<void>;
|
||||||
|
stopSlackProvider: () => Promise<void>;
|
||||||
startSignalProvider: () => Promise<void>;
|
startSignalProvider: () => Promise<void>;
|
||||||
stopSignalProvider: () => Promise<void>;
|
stopSignalProvider: () => Promise<void>;
|
||||||
startIMessageProvider: () => Promise<void>;
|
startIMessageProvider: () => Promise<void>;
|
||||||
@@ -94,11 +111,13 @@ export function createProviderManager(
|
|||||||
logWhatsApp,
|
logWhatsApp,
|
||||||
logTelegram,
|
logTelegram,
|
||||||
logDiscord,
|
logDiscord,
|
||||||
|
logSlack,
|
||||||
logSignal,
|
logSignal,
|
||||||
logIMessage,
|
logIMessage,
|
||||||
whatsappRuntimeEnv,
|
whatsappRuntimeEnv,
|
||||||
telegramRuntimeEnv,
|
telegramRuntimeEnv,
|
||||||
discordRuntimeEnv,
|
discordRuntimeEnv,
|
||||||
|
slackRuntimeEnv,
|
||||||
signalRuntimeEnv,
|
signalRuntimeEnv,
|
||||||
imessageRuntimeEnv,
|
imessageRuntimeEnv,
|
||||||
} = opts;
|
} = opts;
|
||||||
@@ -106,11 +125,13 @@ export function createProviderManager(
|
|||||||
let whatsappAbort: AbortController | null = null;
|
let whatsappAbort: AbortController | null = null;
|
||||||
let telegramAbort: AbortController | null = null;
|
let telegramAbort: AbortController | null = null;
|
||||||
let discordAbort: AbortController | null = null;
|
let discordAbort: AbortController | null = null;
|
||||||
|
let slackAbort: AbortController | null = null;
|
||||||
let signalAbort: AbortController | null = null;
|
let signalAbort: AbortController | null = null;
|
||||||
let imessageAbort: AbortController | null = null;
|
let imessageAbort: AbortController | null = null;
|
||||||
let whatsappTask: Promise<unknown> | null = null;
|
let whatsappTask: Promise<unknown> | null = null;
|
||||||
let telegramTask: Promise<unknown> | null = null;
|
let telegramTask: Promise<unknown> | null = null;
|
||||||
let discordTask: Promise<unknown> | null = null;
|
let discordTask: Promise<unknown> | null = null;
|
||||||
|
let slackTask: Promise<unknown> | null = null;
|
||||||
let signalTask: Promise<unknown> | null = null;
|
let signalTask: Promise<unknown> | null = null;
|
||||||
let imessageTask: Promise<unknown> | null = null;
|
let imessageTask: Promise<unknown> | null = null;
|
||||||
|
|
||||||
@@ -137,6 +158,12 @@ export function createProviderManager(
|
|||||||
lastStopAt: null,
|
lastStopAt: null,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
};
|
};
|
||||||
|
let slackRuntime: SlackRuntimeStatus = {
|
||||||
|
running: false,
|
||||||
|
lastStartAt: null,
|
||||||
|
lastStopAt: null,
|
||||||
|
lastError: null,
|
||||||
|
};
|
||||||
let signalRuntime: SignalRuntimeStatus = {
|
let signalRuntime: SignalRuntimeStatus = {
|
||||||
running: false,
|
running: false,
|
||||||
lastStartAt: null,
|
lastStartAt: null,
|
||||||
@@ -432,6 +459,93 @@ export function createProviderManager(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startSlackProvider = async () => {
|
||||||
|
if (slackTask) return;
|
||||||
|
const cfg = loadConfig();
|
||||||
|
if (cfg.slack?.enabled === false) {
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
running: false,
|
||||||
|
lastError: "disabled",
|
||||||
|
};
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logSlack.debug("slack provider disabled (slack.enabled=false)");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const botToken = resolveSlackBotToken(
|
||||||
|
process.env.SLACK_BOT_TOKEN ?? cfg.slack?.botToken ?? undefined,
|
||||||
|
);
|
||||||
|
const appToken = resolveSlackAppToken(
|
||||||
|
process.env.SLACK_APP_TOKEN ?? cfg.slack?.appToken ?? undefined,
|
||||||
|
);
|
||||||
|
if (!botToken || !appToken) {
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
running: false,
|
||||||
|
lastError: "not configured",
|
||||||
|
};
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logSlack.debug(
|
||||||
|
"slack provider not configured (missing SLACK_BOT_TOKEN/SLACK_APP_TOKEN)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logSlack.info(
|
||||||
|
`starting provider${cfg.slack ? "" : " (no slack config; tokens via env)"}`,
|
||||||
|
);
|
||||||
|
slackAbort = new AbortController();
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
running: true,
|
||||||
|
lastStartAt: Date.now(),
|
||||||
|
lastError: null,
|
||||||
|
};
|
||||||
|
const task = monitorSlackProvider({
|
||||||
|
botToken,
|
||||||
|
appToken,
|
||||||
|
runtime: slackRuntimeEnv,
|
||||||
|
abortSignal: slackAbort.signal,
|
||||||
|
mediaMaxMb: cfg.slack?.mediaMaxMb,
|
||||||
|
slashCommand: cfg.slack?.slashCommand,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
lastError: formatError(err),
|
||||||
|
};
|
||||||
|
logSlack.error(`provider exited: ${formatError(err)}`);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
slackAbort = null;
|
||||||
|
slackTask = null;
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
running: false,
|
||||||
|
lastStopAt: Date.now(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
slackTask = task;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopSlackProvider = async () => {
|
||||||
|
if (!slackAbort && !slackTask) return;
|
||||||
|
slackAbort?.abort();
|
||||||
|
try {
|
||||||
|
await slackTask;
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
slackAbort = null;
|
||||||
|
slackTask = null;
|
||||||
|
slackRuntime = {
|
||||||
|
...slackRuntime,
|
||||||
|
running: false,
|
||||||
|
lastStopAt: Date.now(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const startSignalProvider = async () => {
|
const startSignalProvider = async () => {
|
||||||
if (signalTask) return;
|
if (signalTask) return;
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
@@ -634,6 +748,7 @@ export function createProviderManager(
|
|||||||
const startProviders = async () => {
|
const startProviders = async () => {
|
||||||
await startWhatsAppProvider();
|
await startWhatsAppProvider();
|
||||||
await startDiscordProvider();
|
await startDiscordProvider();
|
||||||
|
await startSlackProvider();
|
||||||
await startTelegramProvider();
|
await startTelegramProvider();
|
||||||
await startSignalProvider();
|
await startSignalProvider();
|
||||||
await startIMessageProvider();
|
await startIMessageProvider();
|
||||||
@@ -652,6 +767,7 @@ export function createProviderManager(
|
|||||||
whatsapp: { ...whatsappRuntime },
|
whatsapp: { ...whatsappRuntime },
|
||||||
telegram: { ...telegramRuntime },
|
telegram: { ...telegramRuntime },
|
||||||
discord: { ...discordRuntime },
|
discord: { ...discordRuntime },
|
||||||
|
slack: { ...slackRuntime },
|
||||||
signal: { ...signalRuntime },
|
signal: { ...signalRuntime },
|
||||||
imessage: { ...imessageRuntime },
|
imessage: { ...imessageRuntime },
|
||||||
});
|
});
|
||||||
@@ -665,6 +781,8 @@ export function createProviderManager(
|
|||||||
stopTelegramProvider,
|
stopTelegramProvider,
|
||||||
startDiscordProvider,
|
startDiscordProvider,
|
||||||
stopDiscordProvider,
|
stopDiscordProvider,
|
||||||
|
startSlackProvider,
|
||||||
|
stopSlackProvider,
|
||||||
startSignalProvider,
|
startSignalProvider,
|
||||||
stopSignalProvider,
|
stopSignalProvider,
|
||||||
startIMessageProvider,
|
startIMessageProvider,
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ const hoisted = vi.hoisted(() => {
|
|||||||
lastStopAt: null,
|
lastStopAt: null,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
},
|
},
|
||||||
|
slack: {
|
||||||
|
running: false,
|
||||||
|
lastStartAt: null,
|
||||||
|
lastStopAt: null,
|
||||||
|
lastError: null,
|
||||||
|
},
|
||||||
signal: {
|
signal: {
|
||||||
running: false,
|
running: false,
|
||||||
lastStartAt: null,
|
lastStartAt: null,
|
||||||
@@ -78,6 +84,8 @@ const hoisted = vi.hoisted(() => {
|
|||||||
stopTelegramProvider: vi.fn(async () => {}),
|
stopTelegramProvider: vi.fn(async () => {}),
|
||||||
startDiscordProvider: vi.fn(async () => {}),
|
startDiscordProvider: vi.fn(async () => {}),
|
||||||
stopDiscordProvider: vi.fn(async () => {}),
|
stopDiscordProvider: vi.fn(async () => {}),
|
||||||
|
startSlackProvider: vi.fn(async () => {}),
|
||||||
|
stopSlackProvider: vi.fn(async () => {}),
|
||||||
startSignalProvider: vi.fn(async () => {}),
|
startSignalProvider: vi.fn(async () => {}),
|
||||||
stopSignalProvider: vi.fn(async () => {}),
|
stopSignalProvider: vi.fn(async () => {}),
|
||||||
startIMessageProvider: vi.fn(async () => {}),
|
startIMessageProvider: vi.fn(async () => {}),
|
||||||
|
|||||||
@@ -148,12 +148,14 @@ const logWsControl = log.child("ws");
|
|||||||
const logWhatsApp = logProviders.child("whatsapp");
|
const logWhatsApp = logProviders.child("whatsapp");
|
||||||
const logTelegram = logProviders.child("telegram");
|
const logTelegram = logProviders.child("telegram");
|
||||||
const logDiscord = logProviders.child("discord");
|
const logDiscord = logProviders.child("discord");
|
||||||
|
const logSlack = logProviders.child("slack");
|
||||||
const logSignal = logProviders.child("signal");
|
const logSignal = logProviders.child("signal");
|
||||||
const logIMessage = logProviders.child("imessage");
|
const logIMessage = logProviders.child("imessage");
|
||||||
const canvasRuntime = runtimeForLogger(logCanvas);
|
const canvasRuntime = runtimeForLogger(logCanvas);
|
||||||
const whatsappRuntimeEnv = runtimeForLogger(logWhatsApp);
|
const whatsappRuntimeEnv = runtimeForLogger(logWhatsApp);
|
||||||
const telegramRuntimeEnv = runtimeForLogger(logTelegram);
|
const telegramRuntimeEnv = runtimeForLogger(logTelegram);
|
||||||
const discordRuntimeEnv = runtimeForLogger(logDiscord);
|
const discordRuntimeEnv = runtimeForLogger(logDiscord);
|
||||||
|
const slackRuntimeEnv = runtimeForLogger(logSlack);
|
||||||
const signalRuntimeEnv = runtimeForLogger(logSignal);
|
const signalRuntimeEnv = runtimeForLogger(logSignal);
|
||||||
const imessageRuntimeEnv = runtimeForLogger(logIMessage);
|
const imessageRuntimeEnv = runtimeForLogger(logIMessage);
|
||||||
|
|
||||||
@@ -478,6 +480,7 @@ export async function startGatewayServer(
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage";
|
| "imessage";
|
||||||
to?: string;
|
to?: string;
|
||||||
@@ -722,11 +725,13 @@ export async function startGatewayServer(
|
|||||||
logWhatsApp,
|
logWhatsApp,
|
||||||
logTelegram,
|
logTelegram,
|
||||||
logDiscord,
|
logDiscord,
|
||||||
|
logSlack,
|
||||||
logSignal,
|
logSignal,
|
||||||
logIMessage,
|
logIMessage,
|
||||||
whatsappRuntimeEnv,
|
whatsappRuntimeEnv,
|
||||||
telegramRuntimeEnv,
|
telegramRuntimeEnv,
|
||||||
discordRuntimeEnv,
|
discordRuntimeEnv,
|
||||||
|
slackRuntimeEnv,
|
||||||
signalRuntimeEnv,
|
signalRuntimeEnv,
|
||||||
imessageRuntimeEnv,
|
imessageRuntimeEnv,
|
||||||
});
|
});
|
||||||
@@ -736,11 +741,13 @@ export async function startGatewayServer(
|
|||||||
startWhatsAppProvider,
|
startWhatsAppProvider,
|
||||||
startTelegramProvider,
|
startTelegramProvider,
|
||||||
startDiscordProvider,
|
startDiscordProvider,
|
||||||
|
startSlackProvider,
|
||||||
startSignalProvider,
|
startSignalProvider,
|
||||||
startIMessageProvider,
|
startIMessageProvider,
|
||||||
stopWhatsAppProvider,
|
stopWhatsAppProvider,
|
||||||
stopTelegramProvider,
|
stopTelegramProvider,
|
||||||
stopDiscordProvider,
|
stopDiscordProvider,
|
||||||
|
stopSlackProvider,
|
||||||
stopSignalProvider,
|
stopSignalProvider,
|
||||||
stopIMessageProvider,
|
stopIMessageProvider,
|
||||||
markWhatsAppLoggedOut,
|
markWhatsAppLoggedOut,
|
||||||
@@ -1593,7 +1600,7 @@ export async function startGatewayServer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch configured providers (WhatsApp Web, Discord, Telegram) so gateway replies via the
|
// Launch configured providers (WhatsApp Web, Discord, Slack, Telegram) so gateway replies via the
|
||||||
// surface the message came from. Tests can opt out via CLAWDIS_SKIP_PROVIDERS.
|
// surface the message came from. Tests can opt out via CLAWDIS_SKIP_PROVIDERS.
|
||||||
if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") {
|
if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") {
|
||||||
try {
|
try {
|
||||||
@@ -1703,6 +1710,9 @@ export async function startGatewayServer(
|
|||||||
startDiscordProvider,
|
startDiscordProvider,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (plan.restartProviders.has("slack")) {
|
||||||
|
await restartProvider("slack", stopSlackProvider, startSlackProvider);
|
||||||
|
}
|
||||||
if (plan.restartProviders.has("signal")) {
|
if (plan.restartProviders.has("signal")) {
|
||||||
await restartProvider(
|
await restartProvider(
|
||||||
"signal",
|
"signal",
|
||||||
@@ -1806,6 +1816,7 @@ export async function startGatewayServer(
|
|||||||
await stopWhatsAppProvider();
|
await stopWhatsAppProvider();
|
||||||
await stopTelegramProvider();
|
await stopTelegramProvider();
|
||||||
await stopDiscordProvider();
|
await stopDiscordProvider();
|
||||||
|
await stopSlackProvider();
|
||||||
await stopSignalProvider();
|
await stopSignalProvider();
|
||||||
await stopIMessageProvider();
|
await stopIMessageProvider();
|
||||||
await stopGmailWatcher();
|
await stopGmailWatcher();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { getQueueSize } from "../process/command-queue.js";
|
|||||||
import { webAuthExists } from "../providers/web/index.js";
|
import { webAuthExists } from "../providers/web/index.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
import { sendMessageSignal } from "../signal/send.js";
|
import { sendMessageSignal } from "../signal/send.js";
|
||||||
|
import { sendMessageSlack } from "../slack/send.js";
|
||||||
import { sendMessageTelegram } from "../telegram/send.js";
|
import { sendMessageTelegram } from "../telegram/send.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
import { getActiveWebListener } from "../web/active-listener.js";
|
import { getActiveWebListener } from "../web/active-listener.js";
|
||||||
@@ -38,12 +39,20 @@ export type HeartbeatTarget =
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage"
|
| "imessage"
|
||||||
| "none";
|
| "none";
|
||||||
|
|
||||||
export type HeartbeatDeliveryTarget = {
|
export type HeartbeatDeliveryTarget = {
|
||||||
channel: "whatsapp" | "telegram" | "discord" | "signal" | "imessage" | "none";
|
channel:
|
||||||
|
| "whatsapp"
|
||||||
|
| "telegram"
|
||||||
|
| "discord"
|
||||||
|
| "slack"
|
||||||
|
| "signal"
|
||||||
|
| "imessage"
|
||||||
|
| "none";
|
||||||
to?: string;
|
to?: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
};
|
};
|
||||||
@@ -53,6 +62,7 @@ type HeartbeatDeps = {
|
|||||||
sendWhatsApp?: typeof sendMessageWhatsApp;
|
sendWhatsApp?: typeof sendMessageWhatsApp;
|
||||||
sendTelegram?: typeof sendMessageTelegram;
|
sendTelegram?: typeof sendMessageTelegram;
|
||||||
sendDiscord?: typeof sendMessageDiscord;
|
sendDiscord?: typeof sendMessageDiscord;
|
||||||
|
sendSlack?: typeof sendMessageSlack;
|
||||||
sendSignal?: typeof sendMessageSignal;
|
sendSignal?: typeof sendMessageSignal;
|
||||||
sendIMessage?: typeof sendMessageIMessage;
|
sendIMessage?: typeof sendMessageIMessage;
|
||||||
getQueueSize?: (lane?: string) => number;
|
getQueueSize?: (lane?: string) => number;
|
||||||
@@ -183,6 +193,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
|||||||
rawTarget === "whatsapp" ||
|
rawTarget === "whatsapp" ||
|
||||||
rawTarget === "telegram" ||
|
rawTarget === "telegram" ||
|
||||||
rawTarget === "discord" ||
|
rawTarget === "discord" ||
|
||||||
|
rawTarget === "slack" ||
|
||||||
rawTarget === "signal" ||
|
rawTarget === "signal" ||
|
||||||
rawTarget === "imessage" ||
|
rawTarget === "imessage" ||
|
||||||
rawTarget === "none" ||
|
rawTarget === "none" ||
|
||||||
@@ -209,6 +220,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
|||||||
| "whatsapp"
|
| "whatsapp"
|
||||||
| "telegram"
|
| "telegram"
|
||||||
| "discord"
|
| "discord"
|
||||||
|
| "slack"
|
||||||
| "signal"
|
| "signal"
|
||||||
| "imessage"
|
| "imessage"
|
||||||
| undefined =
|
| undefined =
|
||||||
@@ -217,6 +229,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
|||||||
: target === "whatsapp" ||
|
: target === "whatsapp" ||
|
||||||
target === "telegram" ||
|
target === "telegram" ||
|
||||||
target === "discord" ||
|
target === "discord" ||
|
||||||
|
target === "slack" ||
|
||||||
target === "signal" ||
|
target === "signal" ||
|
||||||
target === "imessage"
|
target === "imessage"
|
||||||
? target
|
? target
|
||||||
@@ -288,7 +301,13 @@ function normalizeHeartbeatReply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deliverHeartbeatReply(params: {
|
async function deliverHeartbeatReply(params: {
|
||||||
channel: "whatsapp" | "telegram" | "discord" | "signal" | "imessage";
|
channel:
|
||||||
|
| "whatsapp"
|
||||||
|
| "telegram"
|
||||||
|
| "discord"
|
||||||
|
| "slack"
|
||||||
|
| "signal"
|
||||||
|
| "imessage";
|
||||||
to: string;
|
to: string;
|
||||||
text: string;
|
text: string;
|
||||||
mediaUrls: string[];
|
mediaUrls: string[];
|
||||||
@@ -299,6 +318,7 @@ async function deliverHeartbeatReply(params: {
|
|||||||
| "sendWhatsApp"
|
| "sendWhatsApp"
|
||||||
| "sendTelegram"
|
| "sendTelegram"
|
||||||
| "sendDiscord"
|
| "sendDiscord"
|
||||||
|
| "sendSlack"
|
||||||
| "sendSignal"
|
| "sendSignal"
|
||||||
| "sendIMessage"
|
| "sendIMessage"
|
||||||
>
|
>
|
||||||
@@ -369,6 +389,20 @@ async function deliverHeartbeatReply(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel === "slack") {
|
||||||
|
if (mediaUrls.length === 0) {
|
||||||
|
await deps.sendSlack(to, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let first = true;
|
||||||
|
for (const url of mediaUrls) {
|
||||||
|
const caption = first ? text : "";
|
||||||
|
first = false;
|
||||||
|
await deps.sendSlack(to, caption, { mediaUrl: url });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaUrls.length === 0) {
|
if (mediaUrls.length === 0) {
|
||||||
await deps.sendDiscord(to, text, { verbose: false });
|
await deps.sendDiscord(to, text, { verbose: false });
|
||||||
return;
|
return;
|
||||||
@@ -498,6 +532,7 @@ export async function runHeartbeatOnce(opts: {
|
|||||||
sendWhatsApp: opts.deps?.sendWhatsApp ?? sendMessageWhatsApp,
|
sendWhatsApp: opts.deps?.sendWhatsApp ?? sendMessageWhatsApp,
|
||||||
sendTelegram: opts.deps?.sendTelegram ?? sendMessageTelegram,
|
sendTelegram: opts.deps?.sendTelegram ?? sendMessageTelegram,
|
||||||
sendDiscord: opts.deps?.sendDiscord ?? sendMessageDiscord,
|
sendDiscord: opts.deps?.sendDiscord ?? sendMessageDiscord,
|
||||||
|
sendSlack: opts.deps?.sendSlack ?? sendMessageSlack,
|
||||||
sendSignal: opts.deps?.sendSignal ?? sendMessageSignal,
|
sendSignal: opts.deps?.sendSignal ?? sendMessageSignal,
|
||||||
sendIMessage: opts.deps?.sendIMessage ?? sendMessageIMessage,
|
sendIMessage: opts.deps?.sendIMessage ?? sendMessageIMessage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,12 +86,11 @@ export async function listSlackReactions(
|
|||||||
export async function sendSlackMessage(
|
export async function sendSlackMessage(
|
||||||
to: string,
|
to: string,
|
||||||
content: string,
|
content: string,
|
||||||
opts: SlackActionClientOpts & { mediaUrl?: string; replyTo?: string } = {},
|
opts: SlackActionClientOpts & { mediaUrl?: string } = {},
|
||||||
) {
|
) {
|
||||||
return await sendMessageSlack(to, content, {
|
return await sendMessageSlack(to, content, {
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
mediaUrl: opts.mediaUrl,
|
mediaUrl: opts.mediaUrl,
|
||||||
threadTs: opts.replyTo,
|
|
||||||
client: opts.client,
|
client: opts.client,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ export {
|
|||||||
unpinSlackMessage,
|
unpinSlackMessage,
|
||||||
} from "./actions.js";
|
} from "./actions.js";
|
||||||
export { monitorSlackProvider } from "./monitor.js";
|
export { monitorSlackProvider } from "./monitor.js";
|
||||||
|
export { probeSlack } from "./probe.js";
|
||||||
export { sendMessageSlack } from "./send.js";
|
export { sendMessageSlack } from "./send.js";
|
||||||
|
export { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|||||||
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||||
import type {
|
import type {
|
||||||
ReplyToMode,
|
|
||||||
SlackReactionNotificationMode,
|
SlackReactionNotificationMode,
|
||||||
SlackSlashCommandConfig,
|
SlackSlashCommandConfig,
|
||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
@@ -26,7 +25,6 @@ export type MonitorSlackOpts = {
|
|||||||
appToken?: string;
|
appToken?: string;
|
||||||
runtime?: RuntimeEnv;
|
runtime?: RuntimeEnv;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
replyToMode?: ReplyToMode;
|
|
||||||
mediaMaxMb?: number;
|
mediaMaxMb?: number;
|
||||||
slashCommand?: SlackSlashCommandConfig;
|
slashCommand?: SlackSlashCommandConfig;
|
||||||
};
|
};
|
||||||
@@ -135,18 +133,6 @@ type SlackChannelConfigResolved = {
|
|||||||
requireMention: boolean;
|
requireMention: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolveSlackReplyTarget(opts: {
|
|
||||||
replyToMode: ReplyToMode;
|
|
||||||
replyToId?: string;
|
|
||||||
hasReplied: boolean;
|
|
||||||
}): string | undefined {
|
|
||||||
if (opts.replyToMode === "off") return undefined;
|
|
||||||
const replyToId = opts.replyToId?.trim();
|
|
||||||
if (!replyToId) return undefined;
|
|
||||||
if (opts.replyToMode === "all") return replyToId;
|
|
||||||
return opts.hasReplied ? undefined : replyToId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeSlackSlug(raw?: string) {
|
function normalizeSlackSlug(raw?: string) {
|
||||||
const trimmed = raw?.trim().toLowerCase() ?? "";
|
const trimmed = raw?.trim().toLowerCase() ?? "";
|
||||||
if (!trimmed) return "";
|
if (!trimmed) return "";
|
||||||
@@ -353,7 +339,6 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
const groupDmChannels = normalizeAllowList(dmConfig?.groupChannels);
|
const groupDmChannels = normalizeAllowList(dmConfig?.groupChannels);
|
||||||
const channelsConfig = cfg.slack?.channels;
|
const channelsConfig = cfg.slack?.channels;
|
||||||
const dmEnabled = dmConfig?.enabled ?? true;
|
const dmEnabled = dmConfig?.enabled ?? true;
|
||||||
const replyToMode = opts.replyToMode ?? cfg.slack?.replyToMode ?? "off";
|
|
||||||
const reactionMode = cfg.slack?.reactionNotifications ?? "own";
|
const reactionMode = cfg.slack?.reactionNotifications ?? "own";
|
||||||
const reactionAllowlist = cfg.slack?.reactionAllowlist ?? [];
|
const reactionAllowlist = cfg.slack?.reactionAllowlist ?? [];
|
||||||
const slashCommand = resolveSlackSlashCommandConfig(
|
const slashCommand = resolveSlackSlashCommandConfig(
|
||||||
@@ -583,6 +568,14 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
const senderName = sender?.name ?? message.user;
|
const senderName = sender?.name ?? message.user;
|
||||||
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
||||||
|
|
||||||
|
const preview = rawBody.replace(/\s+/g, " ").slice(0, 160);
|
||||||
|
const inboundLabel = isDirectMessage
|
||||||
|
? `Slack DM from ${senderName}`
|
||||||
|
: `Slack message in ${roomLabel} from ${senderName}`;
|
||||||
|
enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
|
||||||
|
contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`,
|
||||||
|
});
|
||||||
|
|
||||||
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
|
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
|
||||||
const body = formatAgentEnvelope({
|
const body = formatAgentEnvelope({
|
||||||
surface: "Slack",
|
surface: "Slack",
|
||||||
@@ -634,7 +627,6 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldLogVerbose()) {
|
if (shouldLogVerbose()) {
|
||||||
const preview = rawBody.replace(/\s+/g, " ").slice(0, 160);
|
|
||||||
logVerbose(
|
logVerbose(
|
||||||
`slack inbound: channel=${message.channel} from=${ctxPayload.From} preview="${preview}"`,
|
`slack inbound: channel=${message.channel} from=${ctxPayload.From} preview="${preview}"`,
|
||||||
);
|
);
|
||||||
@@ -656,7 +648,6 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
target: replyTarget,
|
target: replyTarget,
|
||||||
token: botToken,
|
token: botToken,
|
||||||
runtime,
|
runtime,
|
||||||
replyToMode,
|
|
||||||
textLimit,
|
textLimit,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -685,7 +676,6 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
target: replyTarget,
|
target: replyTarget,
|
||||||
token: botToken,
|
token: botToken,
|
||||||
runtime,
|
runtime,
|
||||||
replyToMode,
|
|
||||||
textLimit,
|
textLimit,
|
||||||
});
|
});
|
||||||
if (shouldLogVerbose()) {
|
if (shouldLogVerbose()) {
|
||||||
@@ -1222,49 +1212,36 @@ async function deliverReplies(params: {
|
|||||||
target: string;
|
target: string;
|
||||||
token: string;
|
token: string;
|
||||||
runtime: RuntimeEnv;
|
runtime: RuntimeEnv;
|
||||||
replyToMode: ReplyToMode;
|
|
||||||
textLimit: number;
|
textLimit: number;
|
||||||
}) {
|
}) {
|
||||||
const chunkLimit = Math.min(params.textLimit, 4000);
|
const chunkLimit = Math.min(params.textLimit, 4000);
|
||||||
let hasReplied = false;
|
|
||||||
for (const payload of params.replies) {
|
for (const payload of params.replies) {
|
||||||
const mediaList =
|
const mediaList =
|
||||||
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||||
const text = payload.text ?? "";
|
const text = payload.text ?? "";
|
||||||
const replyToId = payload.replyToId;
|
|
||||||
if (!text && mediaList.length === 0) continue;
|
if (!text && mediaList.length === 0) continue;
|
||||||
|
|
||||||
if (mediaList.length === 0) {
|
if (mediaList.length === 0) {
|
||||||
for (const chunk of chunkText(text, chunkLimit)) {
|
for (const chunk of chunkText(text, chunkLimit)) {
|
||||||
const threadTs = resolveSlackReplyTarget({
|
const threadTs = undefined;
|
||||||
replyToMode: params.replyToMode,
|
|
||||||
replyToId,
|
|
||||||
hasReplied,
|
|
||||||
});
|
|
||||||
const trimmed = chunk.trim();
|
const trimmed = chunk.trim();
|
||||||
if (!trimmed || trimmed === SILENT_REPLY_TOKEN) continue;
|
if (!trimmed || trimmed === SILENT_REPLY_TOKEN) continue;
|
||||||
await sendMessageSlack(params.target, trimmed, {
|
await sendMessageSlack(params.target, trimmed, {
|
||||||
token: params.token,
|
token: params.token,
|
||||||
threadTs,
|
threadTs,
|
||||||
});
|
});
|
||||||
if (threadTs && !hasReplied) hasReplied = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let first = true;
|
let first = true;
|
||||||
for (const mediaUrl of mediaList) {
|
for (const mediaUrl of mediaList) {
|
||||||
const caption = first ? text : "";
|
const caption = first ? text : "";
|
||||||
first = false;
|
first = false;
|
||||||
const threadTs = resolveSlackReplyTarget({
|
const threadTs = undefined;
|
||||||
replyToMode: params.replyToMode,
|
|
||||||
replyToId,
|
|
||||||
hasReplied,
|
|
||||||
});
|
|
||||||
await sendMessageSlack(params.target, caption, {
|
await sendMessageSlack(params.target, caption, {
|
||||||
token: params.token,
|
token: params.token,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
threadTs,
|
threadTs,
|
||||||
});
|
});
|
||||||
if (threadTs && !hasReplied) hasReplied = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.runtime.log?.(`delivered reply to ${params.target}`);
|
params.runtime.log?.(`delivered reply to ${params.target}`);
|
||||||
|
|||||||
59
src/slack/probe.ts
Normal file
59
src/slack/probe.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { WebClient } from "@slack/web-api";
|
||||||
|
|
||||||
|
export type SlackProbe = {
|
||||||
|
ok: boolean;
|
||||||
|
status?: number | null;
|
||||||
|
error?: string | null;
|
||||||
|
elapsedMs?: number | null;
|
||||||
|
bot?: { id?: string; name?: string };
|
||||||
|
team?: { id?: string; name?: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
||||||
|
if (!timeoutMs || timeoutMs <= 0) return promise;
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
const timeout = new Promise<T>((_, reject) => {
|
||||||
|
timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
||||||
|
});
|
||||||
|
return Promise.race([promise, timeout]).finally(() => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function probeSlack(
|
||||||
|
token: string,
|
||||||
|
timeoutMs = 2500,
|
||||||
|
): Promise<SlackProbe> {
|
||||||
|
const client = new WebClient(token);
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const result = await withTimeout(client.auth.test(), timeoutMs);
|
||||||
|
if (!result.ok) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
status: 200,
|
||||||
|
error: result.error ?? "unknown",
|
||||||
|
elapsedMs: Date.now() - start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
elapsedMs: Date.now() - start,
|
||||||
|
bot: { id: result.user_id ?? undefined, name: result.user ?? undefined },
|
||||||
|
team: { id: result.team_id ?? undefined, name: result.team ?? undefined },
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
const status =
|
||||||
|
typeof (err as { status?: number }).status === "number"
|
||||||
|
? (err as { status?: number }).status
|
||||||
|
: null;
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
status,
|
||||||
|
error: message,
|
||||||
|
elapsedMs: Date.now() - start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/slack/send.ts
Normal file
187
src/slack/send.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import { type FilesUploadV2Arguments, WebClient } from "@slack/web-api";
|
||||||
|
|
||||||
|
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||||
|
import { loadConfig } from "../config/config.js";
|
||||||
|
import { loadWebMedia } from "../web/media.js";
|
||||||
|
import { resolveSlackBotToken } from "./token.js";
|
||||||
|
|
||||||
|
const SLACK_TEXT_LIMIT = 4000;
|
||||||
|
|
||||||
|
type SlackRecipient =
|
||||||
|
| {
|
||||||
|
kind: "user";
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "channel";
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SlackSendOpts = {
|
||||||
|
token?: string;
|
||||||
|
mediaUrl?: string;
|
||||||
|
client?: WebClient;
|
||||||
|
threadTs?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SlackSendResult = {
|
||||||
|
messageId: string;
|
||||||
|
channelId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveToken(explicit?: string) {
|
||||||
|
const cfgToken = loadConfig().slack?.botToken;
|
||||||
|
const token = resolveSlackBotToken(
|
||||||
|
explicit ?? process.env.SLACK_BOT_TOKEN ?? cfgToken ?? undefined,
|
||||||
|
);
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
"SLACK_BOT_TOKEN or slack.botToken is required for Slack sends",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRecipient(raw: string): SlackRecipient {
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
throw new Error("Recipient is required for Slack sends");
|
||||||
|
}
|
||||||
|
const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i);
|
||||||
|
if (mentionMatch) {
|
||||||
|
return { kind: "user", id: mentionMatch[1] };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("user:")) {
|
||||||
|
return { kind: "user", id: trimmed.slice("user:".length) };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("channel:")) {
|
||||||
|
return { kind: "channel", id: trimmed.slice("channel:".length) };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("slack:")) {
|
||||||
|
return { kind: "user", id: trimmed.slice("slack:".length) };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("@")) {
|
||||||
|
const candidate = trimmed.slice(1);
|
||||||
|
if (!/^[A-Z0-9]+$/i.test(candidate)) {
|
||||||
|
throw new Error("Slack DMs require a user id (use user:<id> or <@id>)");
|
||||||
|
}
|
||||||
|
return { kind: "user", id: candidate };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("#")) {
|
||||||
|
const candidate = trimmed.slice(1);
|
||||||
|
if (!/^[A-Z0-9]+$/i.test(candidate)) {
|
||||||
|
throw new Error("Slack channels require a channel id (use channel:<id>)");
|
||||||
|
}
|
||||||
|
return { kind: "channel", id: candidate };
|
||||||
|
}
|
||||||
|
return { kind: "channel", id: trimmed };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveChannelId(
|
||||||
|
client: WebClient,
|
||||||
|
recipient: SlackRecipient,
|
||||||
|
): Promise<{ channelId: string; isDm?: boolean }> {
|
||||||
|
if (recipient.kind === "channel") {
|
||||||
|
return { channelId: recipient.id };
|
||||||
|
}
|
||||||
|
const response = await client.conversations.open({ users: recipient.id });
|
||||||
|
const channelId = response.channel?.id;
|
||||||
|
if (!channelId) {
|
||||||
|
throw new Error("Failed to open Slack DM channel");
|
||||||
|
}
|
||||||
|
return { channelId, isDm: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadSlackFile(params: {
|
||||||
|
client: WebClient;
|
||||||
|
channelId: string;
|
||||||
|
mediaUrl: string;
|
||||||
|
caption?: string;
|
||||||
|
threadTs?: string;
|
||||||
|
maxBytes?: number;
|
||||||
|
}): Promise<string> {
|
||||||
|
const { buffer, contentType, fileName } = await loadWebMedia(
|
||||||
|
params.mediaUrl,
|
||||||
|
params.maxBytes,
|
||||||
|
);
|
||||||
|
const basePayload = {
|
||||||
|
channel_id: params.channelId,
|
||||||
|
file: buffer,
|
||||||
|
filename: fileName,
|
||||||
|
...(params.caption ? { initial_comment: params.caption } : {}),
|
||||||
|
...(contentType ? { filetype: contentType } : {}),
|
||||||
|
};
|
||||||
|
const payload: FilesUploadV2Arguments = params.threadTs
|
||||||
|
? { ...basePayload, thread_ts: params.threadTs }
|
||||||
|
: basePayload;
|
||||||
|
const response = await params.client.files.uploadV2(payload);
|
||||||
|
const parsed = response as {
|
||||||
|
files?: Array<{ id?: string; name?: string }>;
|
||||||
|
file?: { id?: string; name?: string };
|
||||||
|
};
|
||||||
|
const fileId =
|
||||||
|
parsed.files?.[0]?.id ??
|
||||||
|
parsed.file?.id ??
|
||||||
|
parsed.files?.[0]?.name ??
|
||||||
|
parsed.file?.name ??
|
||||||
|
"unknown";
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendMessageSlack(
|
||||||
|
to: string,
|
||||||
|
message: string,
|
||||||
|
opts: SlackSendOpts = {},
|
||||||
|
): Promise<SlackSendResult> {
|
||||||
|
const trimmedMessage = message?.trim() ?? "";
|
||||||
|
if (!trimmedMessage && !opts.mediaUrl) {
|
||||||
|
throw new Error("Slack send requires text or media");
|
||||||
|
}
|
||||||
|
const token = resolveToken(opts.token);
|
||||||
|
const client = opts.client ?? new WebClient(token);
|
||||||
|
const recipient = parseRecipient(to);
|
||||||
|
const { channelId } = await resolveChannelId(client, recipient);
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const textLimit = resolveTextChunkLimit(cfg, "slack");
|
||||||
|
const chunkLimit = Math.min(textLimit, SLACK_TEXT_LIMIT);
|
||||||
|
const chunks = chunkText(trimmedMessage, chunkLimit);
|
||||||
|
const mediaMaxBytes =
|
||||||
|
typeof cfg.slack?.mediaMaxMb === "number"
|
||||||
|
? cfg.slack.mediaMaxMb * 1024 * 1024
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let lastMessageId = "";
|
||||||
|
if (opts.mediaUrl) {
|
||||||
|
const [firstChunk, ...rest] = chunks;
|
||||||
|
lastMessageId = await uploadSlackFile({
|
||||||
|
client,
|
||||||
|
channelId,
|
||||||
|
mediaUrl: opts.mediaUrl,
|
||||||
|
caption: firstChunk,
|
||||||
|
threadTs: opts.threadTs,
|
||||||
|
maxBytes: mediaMaxBytes,
|
||||||
|
});
|
||||||
|
for (const chunk of rest) {
|
||||||
|
const response = await client.chat.postMessage({
|
||||||
|
channel: channelId,
|
||||||
|
text: chunk,
|
||||||
|
thread_ts: opts.threadTs,
|
||||||
|
});
|
||||||
|
lastMessageId = response.ts ?? lastMessageId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const chunk of chunks.length ? chunks : [""]) {
|
||||||
|
const response = await client.chat.postMessage({
|
||||||
|
channel: channelId,
|
||||||
|
text: chunk,
|
||||||
|
thread_ts: opts.threadTs,
|
||||||
|
});
|
||||||
|
lastMessageId = response.ts ?? lastMessageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messageId: lastMessageId || "unknown",
|
||||||
|
channelId,
|
||||||
|
};
|
||||||
|
}
|
||||||
12
src/slack/token.ts
Normal file
12
src/slack/token.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export function normalizeSlackToken(raw?: string): string | undefined {
|
||||||
|
const trimmed = raw?.trim();
|
||||||
|
return trimmed ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSlackBotToken(raw?: string): string | undefined {
|
||||||
|
return normalizeSlackToken(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSlackAppToken(raw?: string): string | undefined {
|
||||||
|
return normalizeSlackToken(raw);
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import type {
|
|||||||
CronFormState,
|
CronFormState,
|
||||||
DiscordForm,
|
DiscordForm,
|
||||||
IMessageForm,
|
IMessageForm,
|
||||||
|
SlackForm,
|
||||||
SignalForm,
|
SignalForm,
|
||||||
TelegramForm,
|
TelegramForm,
|
||||||
} from "./ui-types";
|
} from "./ui-types";
|
||||||
@@ -44,6 +45,7 @@ import {
|
|||||||
loadProviders,
|
loadProviders,
|
||||||
updateDiscordForm,
|
updateDiscordForm,
|
||||||
updateIMessageForm,
|
updateIMessageForm,
|
||||||
|
updateSlackForm,
|
||||||
updateSignalForm,
|
updateSignalForm,
|
||||||
updateTelegramForm,
|
updateTelegramForm,
|
||||||
} from "./controllers/connections";
|
} from "./controllers/connections";
|
||||||
@@ -117,6 +119,11 @@ export type AppViewState = {
|
|||||||
discordSaving: boolean;
|
discordSaving: boolean;
|
||||||
discordTokenLocked: boolean;
|
discordTokenLocked: boolean;
|
||||||
discordConfigStatus: string | null;
|
discordConfigStatus: string | null;
|
||||||
|
slackForm: SlackForm;
|
||||||
|
slackSaving: boolean;
|
||||||
|
slackTokenLocked: boolean;
|
||||||
|
slackAppTokenLocked: boolean;
|
||||||
|
slackConfigStatus: string | null;
|
||||||
signalForm: SignalForm;
|
signalForm: SignalForm;
|
||||||
signalSaving: boolean;
|
signalSaving: boolean;
|
||||||
signalConfigStatus: string | null;
|
signalConfigStatus: string | null;
|
||||||
@@ -269,6 +276,11 @@ export function renderApp(state: AppViewState) {
|
|||||||
discordTokenLocked: state.discordTokenLocked,
|
discordTokenLocked: state.discordTokenLocked,
|
||||||
discordSaving: state.discordSaving,
|
discordSaving: state.discordSaving,
|
||||||
discordStatus: state.discordConfigStatus,
|
discordStatus: state.discordConfigStatus,
|
||||||
|
slackForm: state.slackForm,
|
||||||
|
slackTokenLocked: state.slackTokenLocked,
|
||||||
|
slackAppTokenLocked: state.slackAppTokenLocked,
|
||||||
|
slackSaving: state.slackSaving,
|
||||||
|
slackStatus: state.slackConfigStatus,
|
||||||
signalForm: state.signalForm,
|
signalForm: state.signalForm,
|
||||||
signalSaving: state.signalSaving,
|
signalSaving: state.signalSaving,
|
||||||
signalStatus: state.signalConfigStatus,
|
signalStatus: state.signalConfigStatus,
|
||||||
@@ -283,6 +295,8 @@ export function renderApp(state: AppViewState) {
|
|||||||
onTelegramSave: () => state.handleTelegramSave(),
|
onTelegramSave: () => state.handleTelegramSave(),
|
||||||
onDiscordChange: (patch) => updateDiscordForm(state, patch),
|
onDiscordChange: (patch) => updateDiscordForm(state, patch),
|
||||||
onDiscordSave: () => state.handleDiscordSave(),
|
onDiscordSave: () => state.handleDiscordSave(),
|
||||||
|
onSlackChange: (patch) => updateSlackForm(state, patch),
|
||||||
|
onSlackSave: () => state.handleSlackSave(),
|
||||||
onSignalChange: (patch) => updateSignalForm(state, patch),
|
onSignalChange: (patch) => updateSignalForm(state, patch),
|
||||||
onSignalSave: () => state.handleSignalSave(),
|
onSignalSave: () => state.handleSignalSave(),
|
||||||
onIMessageChange: (patch) => updateIMessageForm(state, patch),
|
onIMessageChange: (patch) => updateIMessageForm(state, patch),
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ import type {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
defaultDiscordActions,
|
defaultDiscordActions,
|
||||||
|
defaultSlackActions,
|
||||||
type CronFormState,
|
type CronFormState,
|
||||||
type DiscordForm,
|
type DiscordForm,
|
||||||
type IMessageForm,
|
type IMessageForm,
|
||||||
|
type SlackForm,
|
||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
} from "./ui-types";
|
} from "./ui-types";
|
||||||
@@ -59,6 +61,7 @@ import {
|
|||||||
logoutWhatsApp,
|
logoutWhatsApp,
|
||||||
saveDiscordConfig,
|
saveDiscordConfig,
|
||||||
saveIMessageConfig,
|
saveIMessageConfig,
|
||||||
|
saveSlackConfig,
|
||||||
saveSignalConfig,
|
saveSignalConfig,
|
||||||
saveTelegramConfig,
|
saveTelegramConfig,
|
||||||
startWhatsAppLogin,
|
startWhatsAppLogin,
|
||||||
@@ -233,7 +236,6 @@ export class ClawdisApp extends LitElement {
|
|||||||
mediaMaxMb: "",
|
mediaMaxMb: "",
|
||||||
historyLimit: "",
|
historyLimit: "",
|
||||||
textChunkLimit: "",
|
textChunkLimit: "",
|
||||||
replyToMode: "off",
|
|
||||||
guilds: [],
|
guilds: [],
|
||||||
actions: { ...defaultDiscordActions },
|
actions: { ...defaultDiscordActions },
|
||||||
slashEnabled: false,
|
slashEnabled: false,
|
||||||
@@ -244,6 +246,29 @@ export class ClawdisApp extends LitElement {
|
|||||||
@state() discordSaving = false;
|
@state() discordSaving = false;
|
||||||
@state() discordTokenLocked = false;
|
@state() discordTokenLocked = false;
|
||||||
@state() discordConfigStatus: string | null = null;
|
@state() discordConfigStatus: string | null = null;
|
||||||
|
@state() slackForm: SlackForm = {
|
||||||
|
enabled: true,
|
||||||
|
botToken: "",
|
||||||
|
appToken: "",
|
||||||
|
dmEnabled: true,
|
||||||
|
allowFrom: "",
|
||||||
|
groupEnabled: false,
|
||||||
|
groupChannels: "",
|
||||||
|
mediaMaxMb: "",
|
||||||
|
textChunkLimit: "",
|
||||||
|
reactionNotifications: "own",
|
||||||
|
reactionAllowlist: "",
|
||||||
|
slashEnabled: false,
|
||||||
|
slashName: "",
|
||||||
|
slashSessionPrefix: "",
|
||||||
|
slashEphemeral: true,
|
||||||
|
actions: { ...defaultSlackActions },
|
||||||
|
channels: [],
|
||||||
|
};
|
||||||
|
@state() slackSaving = false;
|
||||||
|
@state() slackTokenLocked = false;
|
||||||
|
@state() slackAppTokenLocked = false;
|
||||||
|
@state() slackConfigStatus: string | null = null;
|
||||||
@state() signalForm: SignalForm = {
|
@state() signalForm: SignalForm = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
account: "",
|
account: "",
|
||||||
@@ -774,6 +799,12 @@ export class ClawdisApp extends LitElement {
|
|||||||
await loadProviders(this, true);
|
await loadProviders(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSlackSave() {
|
||||||
|
await saveSlackConfig(this);
|
||||||
|
await loadConfig(this);
|
||||||
|
await loadProviders(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
async handleSignalSave() {
|
async handleSignalSave() {
|
||||||
await saveSignalConfig(this);
|
await saveSignalConfig(this);
|
||||||
await loadConfig(this);
|
await loadConfig(this);
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import type {
|
|||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
defaultDiscordActions,
|
defaultDiscordActions,
|
||||||
|
defaultSlackActions,
|
||||||
type DiscordActionForm,
|
type DiscordActionForm,
|
||||||
type DiscordForm,
|
type DiscordForm,
|
||||||
type DiscordGuildChannelForm,
|
type DiscordGuildChannelForm,
|
||||||
type DiscordGuildForm,
|
type DiscordGuildForm,
|
||||||
type IMessageForm,
|
type IMessageForm,
|
||||||
|
type SlackChannelForm,
|
||||||
|
type SlackForm,
|
||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
} from "../ui-types";
|
} from "../ui-types";
|
||||||
@@ -34,10 +37,12 @@ export type ConfigState = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
telegramForm: TelegramForm;
|
telegramForm: TelegramForm;
|
||||||
discordForm: DiscordForm;
|
discordForm: DiscordForm;
|
||||||
|
slackForm: SlackForm;
|
||||||
signalForm: SignalForm;
|
signalForm: SignalForm;
|
||||||
imessageForm: IMessageForm;
|
imessageForm: IMessageForm;
|
||||||
telegramConfigStatus: string | null;
|
telegramConfigStatus: string | null;
|
||||||
discordConfigStatus: string | null;
|
discordConfigStatus: string | null;
|
||||||
|
slackConfigStatus: string | null;
|
||||||
signalConfigStatus: string | null;
|
signalConfigStatus: string | null;
|
||||||
imessageConfigStatus: string | null;
|
imessageConfigStatus: string | null;
|
||||||
};
|
};
|
||||||
@@ -255,10 +260,6 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
|||||||
typeof slack.textChunkLimit === "number"
|
typeof slack.textChunkLimit === "number"
|
||||||
? String(slack.textChunkLimit)
|
? String(slack.textChunkLimit)
|
||||||
: "",
|
: "",
|
||||||
replyToMode:
|
|
||||||
slack.replyToMode === "first" || slack.replyToMode === "all"
|
|
||||||
? slack.replyToMode
|
|
||||||
: "off",
|
|
||||||
reactionNotifications:
|
reactionNotifications:
|
||||||
slack.reactionNotifications === "off" ||
|
slack.reactionNotifications === "off" ||
|
||||||
slack.reactionNotifications === "all" ||
|
slack.reactionNotifications === "all" ||
|
||||||
@@ -492,4 +493,3 @@ function removePathValue(
|
|||||||
delete (current as Record<string, unknown>)[lastKey];
|
delete (current as Record<string, unknown>)[lastKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import { parseList } from "../format";
|
|||||||
import type { ConfigSnapshot, ProvidersStatusSnapshot } from "../types";
|
import type { ConfigSnapshot, ProvidersStatusSnapshot } from "../types";
|
||||||
import {
|
import {
|
||||||
defaultDiscordActions,
|
defaultDiscordActions,
|
||||||
|
defaultSlackActions,
|
||||||
type DiscordActionForm,
|
type DiscordActionForm,
|
||||||
type DiscordForm,
|
type DiscordForm,
|
||||||
type DiscordGuildChannelForm,
|
type DiscordGuildChannelForm,
|
||||||
type DiscordGuildForm,
|
type DiscordGuildForm,
|
||||||
type IMessageForm,
|
type IMessageForm,
|
||||||
|
type SlackActionForm,
|
||||||
|
type SlackForm,
|
||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
} from "../ui-types";
|
} from "../ui-types";
|
||||||
@@ -31,6 +34,11 @@ export type ConnectionsState = {
|
|||||||
discordSaving: boolean;
|
discordSaving: boolean;
|
||||||
discordTokenLocked: boolean;
|
discordTokenLocked: boolean;
|
||||||
discordConfigStatus: string | null;
|
discordConfigStatus: string | null;
|
||||||
|
slackForm: SlackForm;
|
||||||
|
slackSaving: boolean;
|
||||||
|
slackTokenLocked: boolean;
|
||||||
|
slackAppTokenLocked: boolean;
|
||||||
|
slackConfigStatus: string | null;
|
||||||
signalForm: SignalForm;
|
signalForm: SignalForm;
|
||||||
signalSaving: boolean;
|
signalSaving: boolean;
|
||||||
signalConfigStatus: string | null;
|
signalConfigStatus: string | null;
|
||||||
@@ -54,6 +62,8 @@ export async function loadProviders(state: ConnectionsState, probe: boolean) {
|
|||||||
state.providersLastSuccess = Date.now();
|
state.providersLastSuccess = Date.now();
|
||||||
state.telegramTokenLocked = res.telegram.tokenSource === "env";
|
state.telegramTokenLocked = res.telegram.tokenSource === "env";
|
||||||
state.discordTokenLocked = res.discord?.tokenSource === "env";
|
state.discordTokenLocked = res.discord?.tokenSource === "env";
|
||||||
|
state.slackTokenLocked = res.slack?.botTokenSource === "env";
|
||||||
|
state.slackAppTokenLocked = res.slack?.appTokenSource === "env";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
state.providersError = String(err);
|
state.providersError = String(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -136,6 +146,21 @@ export function updateDiscordForm(
|
|||||||
state.discordForm = { ...state.discordForm, ...patch };
|
state.discordForm = { ...state.discordForm, ...patch };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateSlackForm(
|
||||||
|
state: ConnectionsState,
|
||||||
|
patch: Partial<SlackForm>,
|
||||||
|
) {
|
||||||
|
if (patch.actions) {
|
||||||
|
state.slackForm = {
|
||||||
|
...state.slackForm,
|
||||||
|
...patch,
|
||||||
|
actions: { ...state.slackForm.actions, ...patch.actions },
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.slackForm = { ...state.slackForm, ...patch };
|
||||||
|
}
|
||||||
|
|
||||||
export function updateSignalForm(
|
export function updateSignalForm(
|
||||||
state: ConnectionsState,
|
state: ConnectionsState,
|
||||||
patch: Partial<SignalForm>,
|
patch: Partial<SignalForm>,
|
||||||
@@ -437,9 +462,6 @@ export async function saveSlackConfig(state: ConnectionsState) {
|
|||||||
delete slack.textChunkLimit;
|
delete slack.textChunkLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.replyToMode === "off") delete slack.replyToMode;
|
|
||||||
else slack.replyToMode = form.replyToMode;
|
|
||||||
|
|
||||||
if (form.reactionNotifications === "own") {
|
if (form.reactionNotifications === "own") {
|
||||||
delete slack.reactionNotifications;
|
delete slack.reactionNotifications;
|
||||||
} else {
|
} else {
|
||||||
@@ -670,4 +692,3 @@ export async function saveIMessageConfig(state: ConnectionsState) {
|
|||||||
state.imessageSaving = false;
|
state.imessageSaving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export type ProvidersStatusSnapshot = {
|
|||||||
whatsapp: WhatsAppStatus;
|
whatsapp: WhatsAppStatus;
|
||||||
telegram: TelegramStatus;
|
telegram: TelegramStatus;
|
||||||
discord?: DiscordStatus | null;
|
discord?: DiscordStatus | null;
|
||||||
|
slack?: SlackStatus | null;
|
||||||
signal?: SignalStatus | null;
|
signal?: SignalStatus | null;
|
||||||
imessage?: IMessageStatus | null;
|
imessage?: IMessageStatus | null;
|
||||||
};
|
};
|
||||||
@@ -89,6 +90,37 @@ export type DiscordStatus = {
|
|||||||
lastProbeAt?: number | null;
|
lastProbeAt?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SlackBot = {
|
||||||
|
id?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SlackTeam = {
|
||||||
|
id?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SlackProbe = {
|
||||||
|
ok: boolean;
|
||||||
|
status?: number | null;
|
||||||
|
error?: string | null;
|
||||||
|
elapsedMs?: number | null;
|
||||||
|
bot?: SlackBot | null;
|
||||||
|
team?: SlackTeam | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SlackStatus = {
|
||||||
|
configured: boolean;
|
||||||
|
botTokenSource?: string | null;
|
||||||
|
appTokenSource?: string | null;
|
||||||
|
running: boolean;
|
||||||
|
lastStartAt?: number | null;
|
||||||
|
lastStopAt?: number | null;
|
||||||
|
lastError?: string | null;
|
||||||
|
probe?: SlackProbe | null;
|
||||||
|
lastProbeAt?: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type SignalProbe = {
|
export type SignalProbe = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
status?: number | null;
|
status?: number | null;
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export type SlackForm = {
|
|||||||
groupChannels: string;
|
groupChannels: string;
|
||||||
mediaMaxMb: string;
|
mediaMaxMb: string;
|
||||||
textChunkLimit: string;
|
textChunkLimit: string;
|
||||||
replyToMode: "off" | "first" | "all";
|
|
||||||
reactionNotifications: "off" | "own" | "all" | "allowlist";
|
reactionNotifications: "off" | "own" | "all" | "allowlist";
|
||||||
reactionAllowlist: string;
|
reactionAllowlist: string;
|
||||||
slashEnabled: boolean;
|
slashEnabled: boolean;
|
||||||
@@ -168,4 +167,3 @@ export type CronFormState = {
|
|||||||
timeoutSeconds: string;
|
timeoutSeconds: string;
|
||||||
postToMainPrefix: string;
|
postToMainPrefix: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type {
|
|||||||
DiscordActionForm,
|
DiscordActionForm,
|
||||||
DiscordForm,
|
DiscordForm,
|
||||||
IMessageForm,
|
IMessageForm,
|
||||||
|
SlackActionForm,
|
||||||
|
SlackForm,
|
||||||
SignalForm,
|
SignalForm,
|
||||||
TelegramForm,
|
TelegramForm,
|
||||||
} from "../ui-types";
|
} from "../ui-types";
|
||||||
@@ -54,6 +56,11 @@ export type ConnectionsProps = {
|
|||||||
discordTokenLocked: boolean;
|
discordTokenLocked: boolean;
|
||||||
discordSaving: boolean;
|
discordSaving: boolean;
|
||||||
discordStatus: string | null;
|
discordStatus: string | null;
|
||||||
|
slackForm: SlackForm;
|
||||||
|
slackTokenLocked: boolean;
|
||||||
|
slackAppTokenLocked: boolean;
|
||||||
|
slackSaving: boolean;
|
||||||
|
slackStatus: string | null;
|
||||||
signalForm: SignalForm;
|
signalForm: SignalForm;
|
||||||
signalSaving: boolean;
|
signalSaving: boolean;
|
||||||
signalStatus: string | null;
|
signalStatus: string | null;
|
||||||
@@ -68,6 +75,8 @@ export type ConnectionsProps = {
|
|||||||
onTelegramSave: () => void;
|
onTelegramSave: () => void;
|
||||||
onDiscordChange: (patch: Partial<DiscordForm>) => void;
|
onDiscordChange: (patch: Partial<DiscordForm>) => void;
|
||||||
onDiscordSave: () => void;
|
onDiscordSave: () => void;
|
||||||
|
onSlackChange: (patch: Partial<SlackForm>) => void;
|
||||||
|
onSlackSave: () => void;
|
||||||
onSignalChange: (patch: Partial<SignalForm>) => void;
|
onSignalChange: (patch: Partial<SignalForm>) => void;
|
||||||
onSignalSave: () => void;
|
onSignalSave: () => void;
|
||||||
onIMessageChange: (patch: Partial<IMessageForm>) => void;
|
onIMessageChange: (patch: Partial<IMessageForm>) => void;
|
||||||
@@ -78,12 +87,14 @@ export function renderConnections(props: ConnectionsProps) {
|
|||||||
const whatsapp = props.snapshot?.whatsapp;
|
const whatsapp = props.snapshot?.whatsapp;
|
||||||
const telegram = props.snapshot?.telegram;
|
const telegram = props.snapshot?.telegram;
|
||||||
const discord = props.snapshot?.discord ?? null;
|
const discord = props.snapshot?.discord ?? null;
|
||||||
|
const slack = props.snapshot?.slack ?? null;
|
||||||
const signal = props.snapshot?.signal ?? null;
|
const signal = props.snapshot?.signal ?? null;
|
||||||
const imessage = props.snapshot?.imessage ?? null;
|
const imessage = props.snapshot?.imessage ?? null;
|
||||||
const providerOrder: ProviderKey[] = [
|
const providerOrder: ProviderKey[] = [
|
||||||
"whatsapp",
|
"whatsapp",
|
||||||
"telegram",
|
"telegram",
|
||||||
"discord",
|
"discord",
|
||||||
|
"slack",
|
||||||
"signal",
|
"signal",
|
||||||
"imessage",
|
"imessage",
|
||||||
];
|
];
|
||||||
@@ -101,7 +112,14 @@ export function renderConnections(props: ConnectionsProps) {
|
|||||||
return html`
|
return html`
|
||||||
<section class="grid grid-cols-2">
|
<section class="grid grid-cols-2">
|
||||||
${orderedProviders.map((provider) =>
|
${orderedProviders.map((provider) =>
|
||||||
renderProvider(provider.key, props, { whatsapp, telegram, discord, signal, imessage }),
|
renderProvider(provider.key, props, {
|
||||||
|
whatsapp,
|
||||||
|
telegram,
|
||||||
|
discord,
|
||||||
|
slack,
|
||||||
|
signal,
|
||||||
|
imessage,
|
||||||
|
}),
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -135,7 +153,13 @@ function formatDuration(ms?: number | null) {
|
|||||||
return `${hr}h`;
|
return `${hr}h`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderKey = "whatsapp" | "telegram" | "discord" | "signal" | "imessage";
|
type ProviderKey =
|
||||||
|
| "whatsapp"
|
||||||
|
| "telegram"
|
||||||
|
| "discord"
|
||||||
|
| "slack"
|
||||||
|
| "signal"
|
||||||
|
| "imessage";
|
||||||
|
|
||||||
function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
|
function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
|
||||||
const snapshot = props.snapshot;
|
const snapshot = props.snapshot;
|
||||||
@@ -151,6 +175,8 @@ function providerEnabled(key: ProviderKey, props: ConnectionsProps) {
|
|||||||
return snapshot.telegram.configured || snapshot.telegram.running;
|
return snapshot.telegram.configured || snapshot.telegram.running;
|
||||||
case "discord":
|
case "discord":
|
||||||
return Boolean(snapshot.discord?.configured || snapshot.discord?.running);
|
return Boolean(snapshot.discord?.configured || snapshot.discord?.running);
|
||||||
|
case "slack":
|
||||||
|
return Boolean(snapshot.slack?.configured || snapshot.slack?.running);
|
||||||
case "signal":
|
case "signal":
|
||||||
return Boolean(snapshot.signal?.configured || snapshot.signal?.running);
|
return Boolean(snapshot.signal?.configured || snapshot.signal?.running);
|
||||||
case "imessage":
|
case "imessage":
|
||||||
@@ -167,6 +193,7 @@ function renderProvider(
|
|||||||
whatsapp?: ProvidersStatusSnapshot["whatsapp"];
|
whatsapp?: ProvidersStatusSnapshot["whatsapp"];
|
||||||
telegram?: ProvidersStatusSnapshot["telegram"];
|
telegram?: ProvidersStatusSnapshot["telegram"];
|
||||||
discord?: ProvidersStatusSnapshot["discord"] | null;
|
discord?: ProvidersStatusSnapshot["discord"] | null;
|
||||||
|
slack?: ProvidersStatusSnapshot["slack"] | null;
|
||||||
signal?: ProvidersStatusSnapshot["signal"] | null;
|
signal?: ProvidersStatusSnapshot["signal"] | null;
|
||||||
imessage?: ProvidersStatusSnapshot["imessage"] | null;
|
imessage?: ProvidersStatusSnapshot["imessage"] | null;
|
||||||
},
|
},
|
||||||
@@ -949,6 +976,389 @@ function renderProvider(
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
case "slack": {
|
||||||
|
const slack = data.slack;
|
||||||
|
const botName = slack?.probe?.bot?.name;
|
||||||
|
const teamName = slack?.probe?.team?.name;
|
||||||
|
return html`
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">Slack</div>
|
||||||
|
<div class="card-sub">Socket mode status and bot details.</div>
|
||||||
|
|
||||||
|
<div class="status-list" style="margin-top: 16px;">
|
||||||
|
<div>
|
||||||
|
<span class="label">Configured</span>
|
||||||
|
<span>${slack?.configured ? "Yes" : "No"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="label">Running</span>
|
||||||
|
<span>${slack?.running ? "Yes" : "No"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="label">Bot</span>
|
||||||
|
<span>${botName ? botName : "n/a"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="label">Team</span>
|
||||||
|
<span>${teamName ? teamName : "n/a"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="label">Last start</span>
|
||||||
|
<span>${slack?.lastStartAt ? formatAgo(slack.lastStartAt) : "n/a"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="label">Last probe</span>
|
||||||
|
<span>${slack?.lastProbeAt ? formatAgo(slack.lastProbeAt) : "n/a"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${slack?.lastError
|
||||||
|
? html`<div class="callout danger" style="margin-top: 12px;">
|
||||||
|
${slack.lastError}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
${slack?.probe
|
||||||
|
? html`<div class="callout" style="margin-top: 12px;">
|
||||||
|
Probe ${slack.probe.ok ? "ok" : "failed"} ·
|
||||||
|
${slack.probe.status ?? ""}
|
||||||
|
${slack.probe.error ?? ""}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<div class="form-grid" style="margin-top: 16px;">
|
||||||
|
<label class="field">
|
||||||
|
<span>Enabled</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.enabled ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
enabled: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Bot token</span>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
.value=${props.slackForm.botToken}
|
||||||
|
?disabled=${props.slackTokenLocked}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
botToken: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>App token</span>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
.value=${props.slackForm.appToken}
|
||||||
|
?disabled=${props.slackAppTokenLocked}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
appToken: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>DMs enabled</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.dmEnabled ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
dmEnabled: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Enabled</option>
|
||||||
|
<option value="no">Disabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Allow DMs from</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.allowFrom}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
allowFrom: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="U123, U456, *"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Group DMs enabled</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.groupEnabled ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
groupEnabled: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Enabled</option>
|
||||||
|
<option value="no">Disabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Group DM channels</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.groupChannels}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
groupChannels: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="G123, #team"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Reaction notifications</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.reactionNotifications}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
reactionNotifications: (e.target as HTMLSelectElement)
|
||||||
|
.value as "off" | "own" | "all" | "allowlist",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="off">Off</option>
|
||||||
|
<option value="own">Own</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
<option value="allowlist">Allowlist</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Reaction allowlist</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.reactionAllowlist}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
reactionAllowlist: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="U123, U456"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Text chunk limit</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.textChunkLimit}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
textChunkLimit: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="4000"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Media max (MB)</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.mediaMaxMb}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
mediaMaxMb: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="20"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-sub" style="margin-top: 16px;">Slash command</div>
|
||||||
|
<div class="form-grid" style="margin-top: 8px;">
|
||||||
|
<label class="field">
|
||||||
|
<span>Slash enabled</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.slashEnabled ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
slashEnabled: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Enabled</option>
|
||||||
|
<option value="no">Disabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Slash name</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.slashName}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
slashName: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="clawd"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Slash session prefix</span>
|
||||||
|
<input
|
||||||
|
.value=${props.slackForm.slashSessionPrefix}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
slashSessionPrefix: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="slack:slash"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Slash ephemeral</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.slashEphemeral ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
slashEphemeral: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-sub" style="margin-top: 16px;">Channels</div>
|
||||||
|
<div class="card-sub">
|
||||||
|
Add channel ids or #names and optionally require mentions.
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
${props.slackForm.channels.map(
|
||||||
|
(channel, channelIndex) => html`
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="list-main">
|
||||||
|
<div class="form-grid">
|
||||||
|
<label class="field">
|
||||||
|
<span>Channel id / name</span>
|
||||||
|
<input
|
||||||
|
.value=${channel.key}
|
||||||
|
@input=${(e: Event) => {
|
||||||
|
const next = [...props.slackForm.channels];
|
||||||
|
next[channelIndex] = {
|
||||||
|
...next[channelIndex],
|
||||||
|
key: (e.target as HTMLInputElement).value,
|
||||||
|
};
|
||||||
|
props.onSlackChange({ channels: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Allow</span>
|
||||||
|
<select
|
||||||
|
.value=${channel.allow ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.slackForm.channels];
|
||||||
|
next[channelIndex] = {
|
||||||
|
...next[channelIndex],
|
||||||
|
allow:
|
||||||
|
(e.target as HTMLSelectElement).value === "yes",
|
||||||
|
};
|
||||||
|
props.onSlackChange({ channels: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Require mention</span>
|
||||||
|
<select
|
||||||
|
.value=${channel.requireMention ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.slackForm.channels];
|
||||||
|
next[channelIndex] = {
|
||||||
|
...next[channelIndex],
|
||||||
|
requireMention:
|
||||||
|
(e.target as HTMLSelectElement).value === "yes",
|
||||||
|
};
|
||||||
|
props.onSlackChange({ channels: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span> </span>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click=${() => {
|
||||||
|
const next = [...props.slackForm.channels];
|
||||||
|
next.splice(channelIndex, 1);
|
||||||
|
props.onSlackChange({ channels: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
style="margin-top: 8px;"
|
||||||
|
@click=${() =>
|
||||||
|
props.onSlackChange({
|
||||||
|
channels: [
|
||||||
|
...props.slackForm.channels,
|
||||||
|
{ key: "", allow: true, requireMention: false },
|
||||||
|
],
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Add channel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="card-sub" style="margin-top: 16px;">Tool actions</div>
|
||||||
|
<div class="form-grid" style="margin-top: 8px;">
|
||||||
|
${slackActionOptions.map(
|
||||||
|
(action) => html`<label class="field">
|
||||||
|
<span>${action.label}</span>
|
||||||
|
<select
|
||||||
|
.value=${props.slackForm.actions[action.key] ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onSlackChange({
|
||||||
|
actions: {
|
||||||
|
...props.slackForm.actions,
|
||||||
|
[action.key]: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Enabled</option>
|
||||||
|
<option value="no">Disabled</option>
|
||||||
|
</select>
|
||||||
|
</label>`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${props.slackTokenLocked || props.slackAppTokenLocked
|
||||||
|
? html`<div class="callout" style="margin-top: 12px;">
|
||||||
|
${props.slackTokenLocked ? "SLACK_BOT_TOKEN " : ""}
|
||||||
|
${props.slackAppTokenLocked ? "SLACK_APP_TOKEN " : ""}
|
||||||
|
is set in the environment. Config edits will not override it.
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
${props.slackStatus
|
||||||
|
? html`<div class="callout" style="margin-top: 12px;">
|
||||||
|
${props.slackStatus}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<div class="row" style="margin-top: 14px;">
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
?disabled=${props.slackSaving}
|
||||||
|
@click=${() => props.onSlackSave()}
|
||||||
|
>
|
||||||
|
${props.slackSaving ? "Saving…" : "Save"}
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click=${() => props.onRefresh(true)}>
|
||||||
|
Probe
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
case "signal": {
|
case "signal": {
|
||||||
const signal = data.signal;
|
const signal = data.signal;
|
||||||
return html`
|
return html`
|
||||||
@@ -1355,4 +1765,3 @@ function renderProvider(
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user