Skip to content

chat: extract slash-command guardrail to shared helper; apply to WS path #268

Description

@jaylfc

Background

The slash-command guardrail at tinyagentos/routes/chat.py:264-276 rejects /cmd messages in non-DM, non-A2A group channels that are not addressed to an agent (no @<agent> or @all mention). It returns HTTP 400 with a clear error.

This guard runs only on the HTTP post_message path. The WebSocket message branch (chat_ws handler) does not enforce it — so the same input is rejected over HTTP but accepted over WS.

This was flagged by CodeRabbit on PR #267 (review). It is pre-existing — the guard was added in PR #235 (commit `d28dcfa`, Chat Phase 2a) and has always been HTTP-only. PR #267 only added the A2A bypass clause inside the existing HTTP guard.

Why it matters

The safety behavior is transport-dependent. The guard is meant to prevent multi-agent fan-out from a single unaddressed slash command in a group channel; over WS that fan-out is currently still possible.

Proposed fix

  1. Extract the validation into a shared helper, e.g.:
    ```python
    async def _validate_slash_target(content: str, channel: dict | None) -> str | None:
    """Return error message if slash command is unaddressed in a non-DM,
    non-A2A group channel; otherwise None."""
    ```
  2. Call it from `post_message` (replacing the inline block).
  3. Call it from the WebSocket `message` branch in `chat_ws`. Either drop the message silently or send an error frame back over the socket — whichever matches the existing WS error-handling pattern in this file.
  4. Add a regression test in `tests/test_routes_chat_slash_guard.py` exercising the WS path.

Also: defer fix from PR #267 — release-actor attribution

While we're in this area, address the deferred CodeRabbit nit from PR #267:

`beads_bridge.py:287-293` — `task.released` event payload does not include `releaser_id`. `release_task` clears `claimed_by` before the event fires, so the bridge falls back to "agent" for the actor in `format_released`. Fix: have `task_store.release_task` include `releaser_id` in the event payload, then prefer it in the handler.

This is independent of the WS guard fix but small enough to bundle.

Acceptance

  • WS and HTTP paths produce identical guardrail behavior for slash commands.
  • `task.released` actor reflects the user/agent that released the task.
  • Regression tests cover both.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions