feat(channels): add mattermost adapter and wiring

This commit is contained in:
William Valentin
2026-02-16 12:09:44 -08:00
parent 813a0dc5c5
commit de0c1f41b3
16 changed files with 645 additions and 6 deletions
+4 -1
View File
@@ -11,7 +11,7 @@ export interface ConfigHandlerDeps {
* Redact sensitive values from config before returning.
* Replaces API keys, tokens, passwords, and other credentials with "***".
*
* Covers: telegram, discord, slack, matrix, server, models (tiers + fallbacks + local_providers),
* Covers: telegram, discord, slack, matrix, mattermost, server, models (tiers + fallbacks + local_providers),
* web_search, audio, memory.embedding, automation (webhooks + gmail), and mcp server env vars.
*/
export function redactConfig(config: Config): Record<string, unknown> {
@@ -37,6 +37,9 @@ export function redactConfig(config: Config): Record<string, unknown> {
// Matrix
redact(raw.matrix as Record<string, unknown>, 'access_token');
// Mattermost
redact(raw.mattermost as Record<string, unknown>, 'bot_token');
// Server (gateway bearer token)
redact(raw.server as Record<string, unknown>, 'token');
+6
View File
@@ -891,6 +891,7 @@ describe('redactConfig comprehensive credential redaction', () => {
discord: { bot_token: 'dc-secret', allowed_guild_ids: ['g1'], allowed_channel_ids: [], require_mention: true },
slack: { bot_token: 'sl-bot', app_token: 'sl-app', signing_secret: 'sl-sign', allowed_channel_ids: [], require_mention: false },
matrix: { homeserver_url: 'https://matrix.example.org', access_token: 'mx-secret', allowed_room_ids: ['!room1:example.org'], require_mention: true },
mattermost: { server_url: 'https://mattermost.example.org', bot_token: 'mm-secret', allowed_channel_ids: [], require_mention: true, mention_name: 'flynn', poll_interval_ms: 3000 },
server: { tailscale: {}, localhost: true, port: 18800, token: 'bearer-secret', tailscale_identity: false, auth_http: true },
models: {
default: { provider: 'anthropic' as const, model: 'claude', api_key: 'sk-def', auth_token: 'at-def',
@@ -958,6 +959,11 @@ describe('redactConfig comprehensive credential redaction', () => {
expect(getPath(result, 'matrix', 'access_token')).toBe('***');
});
it('redacts mattermost.bot_token', () => {
const result = redactConfig(asRedactInput(makeFullConfig()));
expect(getPath(result, 'mattermost', 'bot_token')).toBe('***');
});
it('redacts server.token', () => {
const result = redactConfig(asRedactInput(makeFullConfig()));
expect(getPath(result, 'server', 'token')).toBe('***');
+2
View File
@@ -31,6 +31,7 @@ function makeBaseConfig(): Config {
whatsapp: undefined,
matrix: undefined,
signal: undefined,
mattermost: undefined,
teams: undefined,
google_chat: undefined,
bluebubbles: undefined,
@@ -48,6 +49,7 @@ describe('discoverServices', () => {
expect.objectContaining({ name: 'telegram', status: 'not_configured' }),
expect.objectContaining({ name: 'matrix', status: 'not_configured' }),
expect.objectContaining({ name: 'signal', status: 'not_configured' }),
expect.objectContaining({ name: 'mattermost', status: 'not_configured' }),
expect.objectContaining({ name: 'teams', status: 'not_configured' }),
expect.objectContaining({ name: 'google_chat', status: 'not_configured' }),
expect.objectContaining({ name: 'bluebubbles', status: 'not_configured' }),
+1
View File
@@ -54,6 +54,7 @@ export function discoverServices(
{ key: 'whatsapp', name: 'whatsapp', description: 'WhatsApp gateway' },
{ key: 'matrix', name: 'matrix', description: 'Matrix bot' },
{ key: 'signal', name: 'signal', description: 'Signal bot (signal-cli)' },
{ key: 'mattermost', name: 'mattermost', description: 'Mattermost bot' },
{ key: 'teams', name: 'teams', description: 'Microsoft Teams bot' },
{ key: 'google_chat', name: 'google_chat', description: 'Google Chat bot' },
{ key: 'bluebubbles', name: 'bluebubbles', description: 'iMessage via BlueBubbles' },