feat(mattermost): add native slash command support#10
Conversation
Register custom slash commands via Mattermost REST API at startup,
handle callbacks via HTTP endpoint on the gateway, and clean up
commands on shutdown.
- New modules: slash-commands.ts (API + registration), slash-http.ts
(callback handler), slash-state.ts (shared state bridge)
- Config schema extended with commands.{native,nativeSkills,callbackPath,callbackUrl}
- Uses oc_ prefix for triggers (oc_status, oc_model, etc.) to avoid
conflicts with Mattermost built-in commands
- Opt-in via channels.mattermost.commands.native: true
- Capability nativeCommands: true exposed for command registry
Closes openclaw#16515
1 similar comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4fc4e6d6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| // Validate token | ||
| if (commandTokens.size > 0 && !commandTokens.has(payload.token)) { |
There was a problem hiding this comment.
Reject callbacks when no registered token is present
The token check is currently conditional on commandTokens.size > 0, which means the callback endpoint accepts any token whenever registration yields zero commands (for example, API permission errors or partial startup failures). In that state, a direct HTTP POST to the callback path can still drive command execution with CommandAuthorized: true, so command authorization effectively falls back to unauthenticated input instead of failing closed.
Useful? React with 👍 / 👎.
| state.commandTokens = new Set(commandTokens); | ||
| state.registeredCommands = registeredCommands; | ||
| state.activeAccount = account; |
There was a problem hiding this comment.
Keep slash command handler state per Mattermost account
Activation stores tokens, registered command IDs, and the handler in one global singleton, so when multiple Mattermost accounts are enabled, each subsequent account startup overwrites the previous account’s slash-command state. This causes callbacks from earlier accounts to fail validation or be routed with the wrong account context, and makes native commands unreliable in multi-account deployments.
Useful? React with 👍 / 👎.
Address Codex review findings: 1. slash-http.ts: Token validation now rejects when commandTokens set is empty (e.g. registration failure). Previously an empty set meant any token was accepted — fail-open vulnerability. 2. slash-state.ts: Replaced global singleton with per-account state Map keyed by accountId. Multi-account deployments no longer overwrite each other's tokens, registered commands, or handlers. The HTTP route dispatcher matches inbound tokens to the correct account. 3. monitor.ts: Updated getSlashCommandState/deactivateSlashCommands calls to pass accountId.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac69dad768
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| WasMentioned: true, | ||
| CommandAuthorized: true, // Slash commands bypass mention requirements | ||
| CommandSource: "native" as const, |
There was a problem hiding this comment.
Enforce sender authorization for native slash commands
The slash-command path unconditionally marks requests as authorized (CommandAuthorized: true) and never applies the DM/group allowlist checks used in the websocket message handler. In deployments that rely on groupPolicy, allowFrom, or access-group command gating, any user who can invoke /oc_* in a channel can run privileged control commands (for example model/session changes), which is a security regression versus normal Mattermost message handling.
Useful? React with 👍 / 👎.
| const commandsRaw = mmConfig?.commands as | ||
| | Partial<import("./slash-commands.js").MattermostSlashCommandConfig> | ||
| | undefined; | ||
| const slashConfig = resolveSlashCommandConfig(commandsRaw); | ||
| const callbackPath = slashConfig.callbackPath; |
There was a problem hiding this comment.
Resolve callback path from the same account config
The HTTP route path is derived only from top-level channels.mattermost.commands, while command registration uses account.config.commands. If an operator sets accounts.<id>.commands.callbackPath (or different per-account paths), Mattermost will post to the account-specific path but the plugin only registers the top-level/default route, so callbacks for that account fail to dispatch.
Useful? React with 👍 / 👎.
| log?.(`mattermost: command /${spec.trigger} already registered (id=${existingCmd.id})`); | ||
| registered.push({ | ||
| id: existingCmd.id, | ||
| trigger: spec.trigger, | ||
| teamId, |
There was a problem hiding this comment.
Do not schedule pre-existing commands for shutdown deletion
When a matching command already exists, it is still pushed into registered, and shutdown cleanup deletes every entry in that list. That means commands that predated this process (or are shared with another instance using the same callback URL) get removed on abort, causing unexpected command loss outside this instance's lifecycle.
Useful? React with 👍 / 👎.
Address Codex follow-up feedback: - Enforce DM/group allowlist + control-command gating for native slash commands (no unconditional CommandAuthorized=true). - Register callback HTTP routes for all configured callbackPath values (top-level + per-account overrides). - Track whether slash commands were created by this process; only delete managed commands on shutdown, leaving pre-existing commands intact.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e80f4cfa5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const path = params.config.callbackPath; | ||
| return `http://${host}:${params.gatewayPort}${path}`; |
There was a problem hiding this comment.
Normalize callbackPath before constructing callback URL
resolveCallbackUrl concatenates callbackPath directly, but route registration normalizes missing leading slashes via plugin HTTP path normalization. If a user sets commands.callbackPath to a common value like api/channels/mattermost/command (no leading /), the command is registered with Mattermost as http://host:portapi/... while the gateway listens on /api/..., so every slash callback misses the handler.
Useful? React with 👍 / 👎.
| if (contentType?.includes("application/json")) { | ||
| return JSON.parse(body) as MattermostSlashCommandPayload; | ||
| } |
There was a problem hiding this comment.
Validate required fields for JSON slash payloads
The JSON branch in parseSlashCommandPayload returns parsed data without checking required fields, unlike the form-encoded branch. Downstream, the handler immediately uses payload.command.replace(...), so a JSON callback with a missing or non-string command causes a runtime exception and 500 instead of a clean 400 rejection; this is reproducible with malformed JSON requests that still carry a valid token.
Useful? React with 👍 / 👎.
- parseSlashCommandPayload JSON branch now validates required fields (token, team_id, channel_id, user_id, command) like the form-encoded branch, preventing runtime exceptions on malformed JSON payloads - normalizeCallbackPath() ensures leading '/' to prevent malformed URLs like 'http://host:portapi/...' when callbackPath lacks a leading slash - Applied in resolveSlashCommandConfig and resolveCallbackUrl Addresses Codex review round 3 (P2 items).
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1956512e06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const host = params.gatewayHost || "localhost"; | ||
| const path = normalizeCallbackPath(params.config.callbackPath); | ||
| return `http://${host}:${params.gatewayPort}${path}`; |
There was a problem hiding this comment.
Require a reachable callback URL before registering commands
When commands.callbackUrl is unset, resolveCallbackUrl falls back to http://localhost:<port>...; in typical deployments where Mattermost is on a different host, that URL is unreachable from the Mattermost server, so slash commands register successfully but invocations never reach this gateway. This makes the feature appear enabled while being non-functional unless users discover and set an override manually.
Useful? React with 👍 / 👎.
| client, | ||
| teamId: team.id, | ||
| callbackUrl, | ||
| commands: DEFAULT_COMMAND_SPECS, |
There was a problem hiding this comment.
Honor nativeSkills when choosing Mattermost commands
The config schema and resolver accept commands.nativeSkills, but registration always passes DEFAULT_COMMAND_SPECS, so enabling nativeSkills has no effect and skill commands are never registered. This creates a silent config mismatch where operators believe a feature is enabled but the command surface does not change.
Useful? React with 👍 / 👎.
| const existingByTrigger = new Map( | ||
| existing.filter((cmd) => cmd.url === callbackUrl).map((cmd) => [cmd.trigger, cmd]), |
There was a problem hiding this comment.
Reconcile existing slash commands when callback URL changes
Existing commands are indexed only when their stored url exactly matches the current callbackUrl, so previously created /oc_* commands with an older callback URL are ignored instead of being updated or replaced. During callback URL migrations this leaves stale commands in Mattermost and breaks seamless cutover to the new endpoint.
Useful? React with 👍 / 👎.
…RL reconciliation
- Reject slash command registration when callbackUrl resolves to
loopback but Mattermost baseUrl is remote; log actionable error
directing user to set commands.callbackUrl explicitly
- Honor commands.nativeSkills: when enabled, dynamically list skill
commands and register them with oc_ prefix alongside built-in commands
- Reconcile existing commands on callback URL change: attempt PUT update,
fallback to delete+recreate for stale commands with mismatched URLs
- Add updateMattermostCommand() for PUT /api/v4/commands/{id}
Addresses Codex review round 4 (P1 + P2 items).
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 972b893257
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const normalized = entries | ||
| .map((entry) => String(entry).trim()) | ||
| .filter(Boolean) | ||
| .map((entry) => entry.toLowerCase()); |
There was a problem hiding this comment.
Normalize slash allowlists like websocket monitor
This normalization only lowercases raw entries, so slash-command auth no longer matches common allowlist formats that the regular Mattermost monitor accepts (for example "*", "@username", and "mattermost:<id>"). In configs that rely on those forms, authorized users are treated as unauthorized for native slash commands, which blocks command execution despite existing working allowlist/pairing settings.
Useful? React with 👍 / 👎.
| const commandsRaw = mmConfig?.commands as | ||
| | Partial<import("./slash-commands.js").MattermostSlashCommandConfig> | ||
| | undefined; | ||
| callbackPaths.add(resolveSlashCommandConfig(commandsRaw).callbackPath); |
There was a problem hiding this comment.
Register callback route from explicit callbackUrl path
Route registration is derived only from callbackPath, so when an operator sets commands.callbackUrl with a non-default pathname but leaves callbackPath unset, commands are created to post to that URL while the gateway still listens on the default path. In that configuration (without a proxy rewrite), Mattermost callbacks hit an unregistered endpoint and native slash commands fail at runtime.
Useful? React with 👍 / 👎.
…ackUrl pathname - normalizeAllowList/isSenderAllowed in slash-http.ts now matches the websocket monitor: strips mattermost:/user:/@ prefixes and supports the '*' wildcard, so configs that work for WS also work for slash cmds - registerSlashCommandRoute extracts pathname from explicit callbackUrl and registers it alongside callbackPath, so callbacks hit a registered route even when callbackUrl uses a non-default pathname Addresses Codex review round 5 (P1 + P2).
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e1a2f4cc65
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (slashConfig.nativeSkills === true) { | ||
| try { | ||
| const { listSkillCommandsForAgents } = | ||
| await import("../../../../src/auto-reply/skill-commands.js"); |
There was a problem hiding this comment.
Resolve skill commands without repo-relative imports
When channels.mattermost.commands.nativeSkills is enabled, this code imports ../../../../src/auto-reply/skill-commands.js, which only exists in a source checkout. Plugin installs are copied into a standalone extensions directory and only install package dependencies (npm install --omit=dev in src/plugins/install.ts), so this relative path is missing in normal npm-installed plugins. The import error is swallowed by the catch, causing native skill slash commands to be silently skipped in production installs.
Useful? React with 👍 / 👎.
| */ | ||
| export function resolveCommandText(trigger: string, text: string): string { | ||
| // Strip the "oc_" prefix to get the original command name | ||
| const commandName = trigger.startsWith("oc_") ? trigger.slice(3) : trigger; |
There was a problem hiding this comment.
Preserve
oc_ skill names when reconstructing command text
This unconditionally strips the oc_ prefix from incoming triggers, but registration keeps skill names that already start with oc_ unchanged (trigger = name.startsWith("oc_") ? name : ...). A skill command named oc_report is therefore registered as /oc_report yet reconstructed as /report, so invocation no longer matches the configured skill command when native skill commands are enabled.
Useful? React with 👍 / 👎.
…rMap for accurate command name resolution - Export listSkillCommandsForAgents and SkillCommandSpec from plugin-sdk/index.ts (removes deep relative import in monitor.ts) - Add originalName field to MattermostCommandSpec for preserving pre-prefix names - Build trigger→originalName map in monitor.ts, thread through slash-state → slash-http - resolveCommandText() now uses triggerMap for accurate name lookup (oc_report → /oc_report correctly, not /report)
|
@codex review this commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d689f8e63
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| commands: getSlashCommandState(account.accountId)?.registeredCommands ?? [], | ||
| log: (msg) => runtime.log?.(msg), | ||
| }) | ||
| .then(() => deactivateSlashCommands(account.accountId)) |
There was a problem hiding this comment.
Deactivate slash state before awaiting command cleanup
This ties deactivateSlashCommands(account.accountId) to the success path of an async network cleanup, which creates a race during reloads: the old monitor can start cleanupSlashCommands, a new monitor can already call activateSlashCommands for the same account, and then this delayed .then(...) clears the freshly activated handler/tokens from the shared slash state map, causing valid callbacks to start failing with 401/503 until another restart; if cleanup throws, deactivation never happens at all, leaving stale state active.
Useful? React with 👍 / 👎.
…nt race Snapshot registered commands, then deactivate state immediately on abort. Prevents race where new monitor activates fresh state that gets wiped by the delayed .then() of the old monitor's cleanup promise.
|
@codex review this commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03d22bca48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/gateway/server-http.ts
Outdated
| if (requestPath.startsWith("/api/channels/")) { | ||
| const token = getBearerToken(req); | ||
| const authResult = await authorizeGatewayConnect({ | ||
| auth: resolvedAuth, | ||
| connectAuth: token ? { token, password: token } : null, |
There was a problem hiding this comment.
Exempt Mattermost slash callback from gateway bearer auth
createGatewayHttpServer now enforces authorizeGatewayConnect for plugin routes under /api/channels/*, but this commit's Mattermost native slash default callback path is /api/channels/mattermost/command (extensions/mattermost/src/mattermost/slash-commands.ts). Mattermost slash callbacks send their command token in the POST payload (not an Authorization bearer token), so requests are rejected with 401 before the plugin slash handler can run, which makes the default native-command configuration non-functional unless users manually move the callback path.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f5d10a226
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (isLoopbackHost(callbackHost) && !isLoopbackHost(mmHost)) { | ||
| runtime.error?.( | ||
| `mattermost: slash commands callbackUrl resolved to ${callbackUrl} (loopback). This is unreachable from Mattermost at ${baseUrl}. Set channels.mattermost.commands.callbackUrl to a reachable URL (e.g. your public reverse proxy URL). Skipping slash command registration.`, | ||
| ); | ||
| throw new Error("unreachable callbackUrl (loopback)"); |
There was a problem hiding this comment.
Don't reject localhost callback URLs via host heuristic
This guard assumes any non-loopback baseUrl means Mattermost runs on a different machine, so it throws when callbackUrl resolves to localhost. That breaks valid co-located deployments where Mattermost is accessed via a public domain but the server process can still reach http://localhost:<port> on the same host; in that setup slash command registration is skipped entirely and native commands never work. The check should avoid hard-failing on this hostname comparison (or make it advisory) because network reachability cannot be inferred from URL hostnames alone.
Useful? React with 👍 / 👎.
|
✅ J’ai relancé la suite de tests rapide en local (pnpm test:fast) sur la branche: 885 files / 5867 tests OK.\n\n |
|
✅ J’ai relancé la suite de tests rapide en local (pnpm test:fast) sur la branche : 885 files / 5867 tests OK.
=> ça ressemble à un secret/config repo manquant (pas un problème dans la PR). Le workflow CI est actuellement en queued (pas encore démarré). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5f285b1f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/auto-reply/command-auth.ts
Outdated
| const rawList = Array.isArray(providerList) ? providerList : globalList; | ||
| if (!Array.isArray(rawList)) { | ||
| return null; // No applicable list found |
There was a problem hiding this comment.
Honor commands.allowFrom override for unmatched providers
When commands.allowFrom is present but neither a provider-specific key nor "*" exists, this path returns null and the caller falls back to channel allowlists. That re-enables command access on providers the operator did not include in commands.allowFrom, which contradicts the new “override” semantics and can unintentionally authorize commands. In this case the resolver should return an empty list (deny) rather than “not configured”.
Useful? React with 👍 / 👎.
| const host = params.gatewayHost || "localhost"; | ||
| const path = normalizeCallbackPath(params.config.callbackPath); | ||
| return `http://${host}:${params.gatewayPort}${path}`; |
There was a problem hiding this comment.
Bracket IPv6 hosts when deriving Mattermost callback URL
The fallback callback URL is built by raw string interpolation, so an IPv6 gatewayHost (for example ::1 or a tailnet IPv6) produces an invalid URL like http://::1:3015/.... That breaks slash-command registration/callbacks for IPv6 deployments unless users set callbackUrl manually.
Useful? React with 👍 / 👎.
|
J’ai appliqué les 2 suggestions Codex de la dernière review :
Commit: 919fa7b |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 919fa7b410
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const gatewayPort = cfg.gateway?.port ?? 3015; | ||
| const callbackUrl = resolveCallbackUrl({ | ||
| config: slashConfig, | ||
| gatewayPort, | ||
| gatewayHost: cfg.gateway?.customBindHost ?? undefined, |
There was a problem hiding this comment.
Use runtime gateway port for slash callback URL
Build the Mattermost slash callback from the actual gateway listener settings instead of cfg.gateway?.port: this code registers commands against the config port, but openclaw gateway run --port <port> starts the server on an override port without rewriting config. In that common setup, Mattermost posts to the wrong callback URL and every slash command invocation fails with connection errors even though registration appears successful.
Useful? React with 👍 / 👎.
|
J’ai appliqué le nouveau commentaire Codex (P1) sur Commit: 281c12c |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 281c12c5ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/gateway/server-http.ts
Outdated
| const isMattermostSlashCommandCallback = requestPath === "/api/channels/mattermost/command"; | ||
| if (requestPath.startsWith("/api/channels/") && !isMattermostSlashCommandCallback) { |
There was a problem hiding this comment.
Exempt all configured slash callback paths from gateway auth
The auth bypass is hardcoded to only "/api/channels/mattermost/command", but this same commit introduces configurable Mattermost callback routing (commands.callbackPath and callbackUrl pathname registration). If an operator sets a different callback path under /api/channels/..., this branch still applies bearer auth (requestPath.startsWith("/api/channels/")), and Mattermost cannot supply that gateway token, so slash invocations will consistently 401 and native commands never execute for that config. The bypass needs to use the registered callback paths rather than one fixed literal.
Useful? React with 👍 / 👎.
|
J’ai adressé le nouveau commentaire Codex (P2) : le bypass d’auth gateway pour On dérive maintenant tous les callback paths Mattermost depuis la config ( Commit: 48b0d2d |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48b0d2d4d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const commandsRaw = account.config.commands as | ||
| | Partial<import("./slash-commands.js").MattermostSlashCommandConfig> | ||
| | undefined; | ||
| const slashConfig = resolveSlashCommandConfig(commandsRaw); | ||
| const slashEnabled = isSlashCommandsEnabled(slashConfig); |
There was a problem hiding this comment.
Preserve global slash settings when reading account commands
monitorMattermostProvider resolves slash behavior only from account.config.commands, but account configs are shallow-merged ({ ...base, ...account } in mergeMattermostAccountConfig), so any per-account commands object replaces the global one entirely. In practice, if operators set channels.mattermost.commands.native: true globally and only override callbackPath/callbackUrl per account, resolveSlashCommandConfig sees native as "auto" and disables native commands for that account, so registration silently never happens.
Useful? React with 👍 / 👎.
| const kind = isDirectMessage | ||
| ? "direct" | ||
| : channelType?.toUpperCase() === "G" | ||
| ? "group" | ||
| : "channel"; |
There was a problem hiding this comment.
Avoid classifying unknown channels as group/channel in slash auth
When channel lookup fails, channelInfo is left null and the fallback classification defaults to "channel"; this means DM slash invocations can be evaluated under group/channel policy (often denied under default allowlist settings) during transient Mattermost API failures. A valid DM command payload should not be rejected just because /channels/{id} was temporarily unavailable.
Useful? React with 👍 / 👎.
|
J’ai adressé les 2 P2 de la dernière review Codex:
Commit: df604c4 |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df604c4829
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : channelInfo | ||
| ? channelType?.toUpperCase() === "G" | ||
| ? "group" | ||
| : "channel" | ||
| : "direct"; |
There was a problem hiding this comment.
Treat unknown slash channel kind as channel
When channel lookup fails (for example due to a transient API/network error), this branch classifies the slash invocation as direct instead of a non-DM type. That makes authorization follow dmPolicy rather than group/channel policy, so commands from channels can be incorrectly accepted even when channel commands are disabled or restricted by allowlists. Defaulting unknown channels to channel avoids this policy bypass.
Useful? React with 👍 / 👎.
|
Addressed Codex P1: when Mattermost channel lookup fails, default slash invocation kind to "channel" (avoid DM-policy bypass). Commit: a0fcf3c |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0fcf3c532
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch (err) { | ||
| log?.(`mattermost: failed to list existing commands: ${String(err)}`); | ||
| } |
There was a problem hiding this comment.
Stop registration when existing-command listing fails
registerSlashCommands catches listMattermostCommands failures and then continues with existing as an empty array. In deployments where slash commands already exist (for example after a previous run), subsequent createMattermostCommand calls can fail as duplicates, which means no command tokens are recorded; the callback handler then rejects all invocations because it treats an empty token set as unauthorized. A transient list failure at startup can therefore make native slash commands consistently return 401 until the process is restarted successfully.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4246541266
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (params.config.callbackUrl) { | ||
| return params.config.callbackUrl; | ||
| } | ||
| let host = params.gatewayHost || "localhost"; |
There was a problem hiding this comment.
Avoid advertising wildcard bind host in callback URL
When gatewayHost comes from gateway.customBindHost, this path will emit callback URLs like http://0.0.0.0:3015/... (or http://[::]:...) for common bind-only configs. Those wildcard addresses are valid listen addresses but not routable callback destinations, so Mattermost slash commands register but cannot invoke the gateway unless the operator manually sets commands.callbackUrl. Treat wildcard bind hosts as non-advertisable and require/derive a reachable hostname instead.
Useful? React with 👍 / 👎.
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Add native slash command support to the Mattermost plugin, bringing it toward parity with Discord, Telegram, and Slack which already register native commands.
Closes openclaw#16515
What this PR does
POST /api/v4/commands) at startup, per team/api/channels/mattermost/command) receives POSTs from MM when a user invokes a command, validates the token, reconstructs the command text, and routes through the standard inbound reply pipelineDELETE /api/v4/commands/{id}commands.{native, nativeSkills, callbackPath, callbackUrl}channels.mattermost.commands.native: trueto enable (autodefaults to false)oc_prefix (e.g./oc_status,/oc_model) to avoid conflicts with Mattermost built-in commandsNew files
slash-commands.tsslash-http.tsslash-state.tsConfig example
{ "channels": { "mattermost": { "commands": { "native": true, "callbackUrl": "https://my-gateway.example.com/api/channels/mattermost/command" } } } }Status
tsc --noEmitzero errors)oc_prefix vs bare command namesOpen questions
oc_prefix (/oc_status) to avoid conflicts with MM built-ins. Should we make this configurable? Or try bare names?callbackUrl, it derives fromgateway.customBindHost + port. In practice, most setups behind a reverse proxy will need to setcallbackUrlexplicitly.