Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

219 lines
11 KiB
Markdown

# Universal slash commands
One command catalog, every shipped surface. A user types `/settings model` in
the floating chat, a Discord channel, or a Telegram DM, and the same command
runs — discovered from one source, rendered natively per surface.
## Why one catalog
Before this, four disconnected slash systems existed: the web composer parsed
slashes only at send-time (no menu), the Cmd+K palette had its own nav
vocabulary, Discord had native application commands, and Telegram had almost
nothing. Nothing shared
a definition. This system makes **`@elizaos/plugin-commands` the single
source of truth** and exposes it over HTTP so every surface consumes the same
list.
```
@elizaos/plugin-commands (per-runtime registry)
DEFAULT_COMMANDS + navigationCommandDefinitions() + skill/custom commands
getCatalogCommands(surface) / getConnectorCommands(surface)
each definition → serializeCommand() ── wire-safe, no functions
┌──────────────────┬───────┴────────┐
GET /api/commands getConnectorCommands getConnectorCommands
?surface=gui ("discord") ("telegram")
│ │ │
Web composer Discord native Telegram
inline menu app commands setMyCommands +
(SlashCommandMenu) (application.commands) bot.command handlers
```
`GET /api/commands` is a pure projection: the route
(`packages/agent/src/api/commands-routes.ts`) calls
`getCatalogCommands(surface, { activeViewId })` and serves the result. Every
field — `surfaces`, `requiresAuth`, `requiresElevated`, `category`,
`args[].dynamicChoices`, `target`, `icon`, `source` — comes from the
`CommandDefinition` via `serializeCommand()` (`src/serialize.ts`). Nothing is
fabricated at the HTTP boundary.
## The command model
A `CommandDefinition` (see `src/types.ts`) carries these dimensions beyond name +
description:
- **`surfaces?: CommandSurface[]`** — which client surfaces it appears on
(`gui` · `discord` · `telegram`, with `tui` reserved in the contract). Absent
= all known surfaces. e.g. `/clear`, `/fullscreen`, and `/transcribe` are
`["gui"]`, so they are filtered off chat connectors and reserved surfaces.
- **`target?: CommandTarget`** — what it *does*, surface-agnostically. Absent =
`{ kind: "agent" }`:
- `{ kind: "navigate", path, tab?, viewId?, section? }` — jump to a view /
sub-view. GUI selects the tab/section; chat connectors reply with a deep
link.
- `{ kind: "agent", action? }` — send the command text to the agent; a
deterministic `*_COMMAND` action produces the reply. Works on every surface.
- `{ kind: "client", clientAction }` — a pure-client behavior (clear chat,
toggle fullscreen, toggle transcription). Local client only.
- **`icon?: string`** — a lucide icon hint for menu rendering.
- **`views?: string[]`** — view-scoping (#8798): the command is surfaced only
while one of these views is the active foreground surface. Omitted = global.
Arguments (`args[]`) can declare static `choices` or a `dynamicChoices` source
(a `CommandArgSource` — e.g. `views` · `settings-sections`) that each surface
resolves against its own live data. `serializeCommand()` (`src/serialize.ts`)
drops function-valued `choices` and carries `dynamicChoices` through so the
catalog is always JSON-safe over the wire. Navigation + client commands are
first-class `CommandDefinition`s in `src/navigation-commands.ts`
(`navigationCommandDefinitions()`); the catalog unions them with the agent
registry, so all three target kinds flow through one serializer.
## The catalog
### Navigation (target: `navigate`) — `src/navigation-commands.ts`
| Command | Destination |
|---|---|
| `/settings [section]` | settings tab; `section` arg (`dynamicChoices: settings-sections`) jumps to a sub-view (model→`ai-model`, voice, connectors, security, …) |
| `/chat` | chat surface |
| `/views [view]` | apps & views launcher; `view` arg `dynamicChoices: views` |
| `/orchestrator` | orchestrator workbench view |
| `/character` | character editor |
| `/knowledge` | knowledge & documents |
| `/wallet` | wallet & inventory |
| `/automations` | automations & heartbeats |
| `/tasks` | tasks view |
| `/skills` | skills library |
| `/plugins` | installed plugins |
| `/logs` | logs view |
| `/database` | database browser |
### Client (target: `client`) — `surfaces: ["gui"]`
| Command | Action |
|---|---|
| `/clear` | clear the current chat thread |
| `/fullscreen` | toggle full-screen chat |
| `/transcribe` | toggle long-form transcription mode |
### Agent capability (target: `agent`) — `src/registry.ts` `DEFAULT_COMMANDS`
`/help` `/commands` `/status` `/context` `/whoami` · `/stop` `/restart`
`/reset` `/new` `/compact` · `/think` `/verbose` `/reasoning` `/elevated`
`/model` `/models` `/usage` `/queue` · `/allowlist` `/approve` `/subagents` ·
`/tts` · `/bash` (and `/config` `/debug`, gated off by default) · plus
`skill-<slug>` commands registered from loaded skills and any custom actions the
user has defined. These flow to the agent and reply in-channel. Auth-gated
commands (`/restart` `/reset` `/compact` `/elevated` `/allowlist` `/approve`
`/subagents` `/bash` `/config` `/debug`) carry `requiresAuth: true`, which survives
serialization onto the wire.
### Design decisions — what is *not* a command
- **No natural-language similes** on command actions — the LLM would misroute
"I need help" to `/help`. Commands are slash-only.
- `/voice` is deliberately **not** a navigation command; it's owned by `/tts`
(toggle text-to-speech). Voice *settings* are reached via `/settings voice`.
- Connector navigation degrades gracefully: a Discord/Telegram user has no app
view to jump to, so `navigate` commands reply with a destination + deep link
rather than failing.
- `client` commands declare `surfaces: ["gui"]`, so they are filtered out of
connector and reserved surfaces entirely (`/fullscreen` makes no sense in
Telegram).
- `/new` is an **agent** command (a new conversation is a runtime concern), not a
client command — only `/clear`, `/fullscreen`, and `/transcribe` are `client`.
## Per-surface rendering
### Web / desktop chat — the floating composer
`ContinuousChatOverlay` (the always-present ambient composer) gets an inline
autocomplete menu (`SlashCommandMenu` + `useSlashMenu`):
- Type `/` → dark-glass menu floats above the bar listing all `gui` commands.
- Type `/se` → fuzzy-ranked filter (alias prefix > native name > description).
- **Tab** completes the highlighted command (`/settings ` — drills into args).
- **Enter** runs the highlighted command. Arrow keys move; **Esc** dismisses
(keeps the draft); click/`pointerdown` executes.
- `/settings ` shows the section choices (model · voice · connectors · …);
`/settings model` → Enter navigates to the `ai-model` settings sub-view.
- Navigation runs client-side (`setTab` / `eliza:navigate:settings` /
`eliza:navigate:view`); client commands run overlay/app effects; deterministic
agent commands (`/status`, `/model gpt-5`, `/think high`, `/reset`, …) resolve
through registered `*_COMMAND` actions before inference; pipeline-owned agent
commands still route through the normal send pipeline. Combobox a11y
(`role=combobox`, `aria-expanded`, `aria-activedescendant`).
Catalog source: `GET /api/commands?surface=gui` (merged client-side with saved
custom commands + custom actions). See `packages/ui/src/chat/slash-menu.ts`
(pure logic, unit-tested) and `useSlashCommandController.ts`.
### Discord — native application commands
`plugins/plugin-discord` maps `getConnectorCommands("discord")`
`DiscordSlashCommand[]` and registers them via the existing
`DISCORD_REGISTER_COMMANDS``client.application.commands.set(...)` path,
*alongside* the existing built-ins (built-ins win on name collisions, so the
role-gated `/help`/`/status`/`/model`/`/settings` keep their behavior). The
`section` arg becomes a string option with choices. On invocation: deterministic
`agent` commands answer locally through `resolveCommand`;
pipeline-owned `agent` commands route through the message pipeline and reply
(deferReply→editReply); `navigate` commands reply (ephemeral) with the
destination + deep link.
### Telegram — `setMyCommands` + handlers
`plugins/plugin-telegram` calls `bot.telegram.setMyCommands(getTelegramBotCommands())`
after launch (so commands appear in Telegram's `/` menu) and registers
`bot.command(name, handler)` per catalog entry. Deterministic `agent` commands
answer locally through `resolveCommand`; pipeline-owned `agent`
commands force a reply through the message pipeline even when
`TELEGRAM_AUTO_REPLY` is off (an explicit command is explicit intent);
`navigate` commands reply with the destination + optional deep link. Command
names are sanitized to Telegram's `[a-z0-9_]{1,32}`.
### Future Non-GUI Surfaces
The command catalog still accepts a `surface` query parameter so future
non-GUI hosts can ask for scoped commands without changing the API shape. No
concrete terminal UI package ships today; GUI clients use the same deterministic
`agent` / `navigate` / `client` command classification described above.
## Verification status
- **Web (gui):** live-verified — Storybook story + Playwright screenshots
(desktop + mobile: all-commands / filtered / sections / filtered-section),
22 pure-logic tests + 12 jsdom integration tests, all green.
- **plugin-commands:** 51 unit tests (catalog, surface + view filtering,
serialization, settings-section resolution, connector mapping, navigation
command defs, dispatch/handlers) + the route handler's 17 tests
(`packages/agent/src/api/commands-routes.test.ts` drives `handleCommandsRoutes`
with mocked `json`/`error`: surface filtering, auth pass-through, dynamic-choice
emission; `commands-routes.real-server.test.ts` exercises the wire over a real
loopback socket).
- **Discord / Telegram:** code-complete + unit-tested (mapping + dispatch
branching). End-to-end requires live bot tokens, not available here —
exercised at the unit level only.
## Adding a command
```ts
import { registerCommand } from "@elizaos/plugin-commands";
registerCommand({
key: "my-view",
nativeName: "myview",
description: "Open my custom view",
textAliases: ["/myview"],
scope: "both",
category: "docks",
surfaces: ["gui"], // omit for every known surface
target: { kind: "navigate", viewId: "my-view", path: "/my-view" },
});
```
It appears automatically in the web menu and, if a connector surface is listed,
Discord/Telegram registration — no per-surface wiring.
```