Skip to content

CL-6499: Fix /<agent> slash-command grammar regression, friendlier unknown-command message - #233

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-6499-slash-commands
Aug 21, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-6499-slash-commands

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

The owner asked for typing /jimmy in workbench chat to invoke that agent directly, the same way @Jimmy does. Turns out this mechanism already exists and is already wired up end-to-end — no new command system needed:

  • packages/commands already has a generic /name args and @name args grammar, a command registry, and createWorkflowCommandPlugin, which derives one command per tenant per invitable agent definition (never a hardcoded list — CL-6451/CL-6471 already built this).
  • packages/chat/src/routes.ts's dispatchWorkbenchCommand already intercepts a leading / on an incoming message and dispatches through that registry.
  • startWorkflowCommand / forcedRecipientAddress in packages/chat/src/workbench-service.ts already routes a command to a resident agent's existing run, or invites+launches it.
  • The chat-ui composer already forwards any typed text starting with / straight through as a message when it doesn't match one of the composer's own fixed UI commands (/invite, /summarize, /routine, /agents, /help) — so /jimmy hi was already reaching the backend command intercept with no client change needed.

So /jimmy throw me a gif for shipping code already worked today, as long as an agent named jimmy is invitable in the tenant. This PR fixes two real gaps found while verifying that:

  1. Regression fix: packages/commands/src/grammar.ts's parseWithPrefix only split on the first space — it never validated the command-name shape. A plain message that merely starts with / (a filesystem path like /usr/local/bin, a URL) was parsed as a command name and, on no match, answered with a raw "Unknown command: /usr/local/bin" message that replaced the user's own text on the timeline. Command names are now constrained to [\w-]+ — the same shape the composer's own activeSlashQuery already commits to — so a path-shaped message now posts normally.
  2. Friendlier unknown-command message: dispatch.ts's unknown-command result used to be a bare "Unknown command: /nope". It now names what commands ARE available ("Unknown command: /nope. Available: /jimmy, /scout.", or a "no agent commands are available yet" message when the tenant has none) — never a silent no-op, never a raw error with no next step.

What I checked before building anything

  • packages/chat-ui/src/slash-commands.ts — the composer's own fixed UI-command catalog (invite/summarize/routine/agents/help). Confirmed it does NOT intercept unmatched /word text; that falls through to an ordinary send().
  • packages/chat/src/mentions.ts + workbench-service.ts's forcedRecipientAddress — the hook the task suggested. Already exactly what startWorkflowCommand uses.
  • packages/agent-directory — confirmed the command registry derives from listInvitableDefinitions per tenant, not a hardcoded agent list.
  • packages/commands — the actual command grammar/registry/dispatch package, already fully wired into the hub (apps/hub/src/index.ts registers createWorkflowCommandPlugin).

Discoverability/autocomplete: deferred, per the task's own escape hatch. The composer's slash popover only lists its fixed 5 UI commands today; wiring it to also list per-tenant agent commands (fetching GET /commands, merging, handling arbitrary execution results client-side) is a larger change than this timebox allows. Working invocation already existed and is now regression-fixed; discovery is left as a follow-up.

Tests (red then green, tests-first commit)

  • packages/commands/test/grammar.test.ts — path-shaped slash text (/usr/local/bin) parses to undefined; hyphenated names still parse.
  • packages/commands/test/dispatch.test.ts — unknown command names available commands; path-shaped text never dispatches.
  • packages/commands/test/workflow-commands.test.ts — /jimmy <text> routes to the definition named jimmy with the remainder as args (proves the agent-directory-derived, non-hardcoded path).
  • packages/chat/test/commands.test.ts — full route-level test: /jimmy throw me a gif for shipping code → workflow-started for jimmy, mail delivered with that text; a path-shaped message posts normally and is never swallowed; unknown-command message updated.

Verification (partial — see note)

Per an urgent note mid-task: the owner's machine OOM'd from concurrent lanes running full-monorepo bun run check. I did not run repo-root bun run check or repo-root lint.

Ran only, package-scoped, one at a time:

  • packages/commands: bun test → 30 pass, 0 fail
  • packages/chat: bun test → 640 pass, 24 skip, 0 fail (full package suite, not just the touched file)

Not run: repo-root typecheck, repo-root lint, full-workspace bun run check. CI should cover these.

…regression

Covers CL-6499: /jimmy <text> should route to the agent named jimmy via
the existing workflow-command plugin, an unknown command should name
what IS available, and a plain message that merely starts with '/'
(e.g. a path) must never be misread as a command attempt.
A message that merely starts with '/' — a path, a URL — used to be
parsed as a command name (spaces-only split, no shape check) and
answered with a raw 'Unknown command' error instead of posting
normally. Command names are now constrained to the same [\w-]+ shape
the composer's own slash popover already commits to.

Also makes the unknown-command message name what commands ARE
available (built-ins plus every invitable agent's own workflow
command) instead of a bare error, since every invitable agent already
gets a /<name> command for free via createWorkflowCommandPlugin — this
is how '/jimmy hello' invokes Jimmy directly today, with no new
command system needed.
@TheGreatAxios
TheGreatAxios merged commit bf4a0b5 into main Aug 21, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-6499-slash-commands branch August 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant