-
Notifications
You must be signed in to change notification settings - Fork 2
slash_commands
Slash commands are quick actions triggered by typing / in the chat input. Commands come from three sources, merged by the registry (frontend/src/commands/registry.ts):
-
Product commands (
source: 'product'): declared in a product'sproduct.yamlundercommands:, with the prompt inline or in acommands/*.mdfile. This is where builder slash commands live (agent_go/internal/agentworksproduct/product.yaml). The owning surface registers them on mount viasetProductCommands()and clears them on unmount. -
Built-in commands (
source: 'builtin'): hardcoded infrontend/src/commands/builtin-commands.tsx. Only/pulseremains — it is an async frontend API call no static prompt can express. -
User commands (
source: 'user'): custom prompt shortcuts stored inworkspace-docs/commands/custom/.
-
Trigger: Type
/in the chat input to open the command picker dialog. -
Registry: A unified command registry (
frontend/src/commands/) merging product, built-in, and user commands.
All commands share a single CommandDefinition interface:
interface CommandDefinition {
command: string // Slash command name (e.g. "design-plan")
aliases?: string[] // Compatibility names resolving to this command, without menu entries
searchTerms?: string[] // Searchable context for a command whose choices live in a picker
description: string // Shown in the picker dialog
icon: ReactNode // Lucide icon
modes?: ModeCategory[] // If set, only visible in these modes (empty = all)
requiredWorkflowMode?: 'plan' | 'eval' | 'output'
requiredWorkshopMode?: WorkshopMode | WorkshopMode[]
validate?: (ctx: CommandContext) => string | null
hidden?: boolean // Executable but not shown in picker (e.g. "compact")
menuHidden?: boolean // Retained shortcut: executable under access checks, without a menu row
source: 'builtin' | 'user' | 'product'
execute: (ctx: CommandContext) => void
}The CommandContext provides everything an execute function needs:
interface CommandContext {
beforeSlash: string // Text typed before the / trigger
activeTabId: string
tabSessionId: string | null
tabConfig: any
isSummarizing: boolean
isStreaming: boolean
onSubmit: (msg: string) => void
openDialog: (name: string) => void
openResumeDialog?: () => void
setTabConfig: (tabId: string, config: any) => void
addToast: (msg: string, type: 'success' | 'error' | 'info') => void
handleSummarize: (ctx?: string) => void
handleCompact: (ctx?: string) => void
getAppStore: () => any
getWorkspaceStore: () => any
getWorkflowStore: () => any
workflowMode?: 'plan' | 'eval' | 'output'
workshopMode?: 'workshop' | 'run'
workflowPhaseId?: string
}// Get all visible commands, optionally filtered by chat mode and workshop mode
getCommands(mode?: ModeCategory, workshopMode?: WorkshopMode): CommandDefinition[]
// Find a visible command by name for the current mode
findCommand(name: string): CommandDefinition | undefined
// Replace user commands (called after loading from API)
setUserCommands(cmds: CommandDefinition[]): void
// Fetch user commands from API and register them
loadAndRegisterUserCommands(): Promise<void>
// Register the active product's manifest-owned commands (called by the
// owning surface on mount; cleared with an empty list on unmount)
setProductCommands(cmds: CommandDefinition[]): voidBuilder slash commands are declared in agent_go/internal/agentworksproduct/product.yaml under commands:, with prompts in commands/*.md next to it. The workflow surface fetches them from the agentworks agent profile and registers them via setProductCommands(); the adapter (frontend/src/commands/agentworksProductCommands.tsx) owns the shared workflow-mode semantics (workflow mode, plan phase, workshop gate) and the {{context}} substitution. Kind-backed commands submit a message asking the agent to call get_workflow_command_guidance with the matching kind, and the backend returns the canonical guided-flow text from agent_go/cmd/server/guidance/templates/.
| Command | Aliases | Backend Kind |
|---|---|---|
/design-plan |
design-plan |
|
/run-plan-drift (hidden; Pulse tab button) |
/review-artifact-drift |
review-artifact-drift |
/design-dashboard |
/design-reporting-ui |
design-reporting-ui |
/setup-goals |
/define-success |
setup-goals |
/run-goal-work |
/strategy-auditor, /goal-advisor
|
strategy-auditor |
/run-technical-review (hidden; Pulse tab button; + 7 hidden pulse-review-* focus shortcuts) |
/pulse-review |
engineering-review then pulse-fixer
|
/run-architecture-review (hidden; Pulse tab button) |
prompt only (references/architecture-review.md) |
|
/review-code |
design-plan (architecture focus) |
|
/backup |
prompt only | |
/publish |
prompt only | |
/notify |
prompt only | |
/pulse (hardcoded builtin, not yaml) |
scheduler API call |
There is no /ops-review, /improve-report, /improve-knowledge, /improve-learnings, or /improve-database command. ops-review and specialize-advisors are builder-reference docs loaded by review turns that need them, not commands; the improve-* checklists were removed.
The workflow command source of truth is split across:
- Product manifest:
agent_go/internal/agentworksproduct/product.yaml+commands/*.md - Product adapter:
frontend/src/commands/agentworksProductCommands.tsx - Backend guidance kind registry:
agent_go/cmd/server/guidance/guidance.go - Backend guidance templates:
agent_go/cmd/server/guidance/templates/**/<kind>.md
When adding or removing a workflow guidance command, keep those places in sync. The manifest test (agent_go/internal/agentworksproduct/commands_test.go) locks the yaml contract.
Add an entry under commands: in the product's product.yaml and a prompt file next to it:
- name: my-command
description: Does something useful
icon: terminal
aliases: [my-alias] # optional
menu_hidden: true # optional: executable without a menu row
file: commands/my-command.md{{context}} in the prompt is replaced with whatever the user typed before the slash. For workflow guidance commands, also add the backend guidance kind in agent_go/cmd/server/guidance/guidance.go and the markdown template under agent_go/cmd/server/guidance/templates/. Only commands no static prompt can express (today: /pulse, an async API call) belong in frontend/src/commands/builtin-commands.tsx.
Users can create custom prompt shortcut commands from the UI. These are stored as workspace files and appear alongside built-in commands in the / picker.
Stored at commands/custom/{name}/COMMAND.md:
---
name: quick-review
description: Review current code changes
icon: eye
modes: []
---
Review my current code changes for bugs, security issues, and performance problems.
{{context}}| Field | Required | Description |
|---|---|---|
name |
Yes | Command name (alphanumeric, hyphens, underscores) |
description |
Yes | Brief description shown in the command picker |
icon |
No | Icon name from available set (default: terminal) |
modes |
No | Array of modes to restrict visibility. Empty [] = all modes |
terminal, zap, eye, code, file-text, message-circle, search, bookmark, star
Use {{context}} in the prompt template to include any text the user typed before the / trigger. For example, if the user types:
fix the login bug /quick-review
The text fix the login bug is substituted into the {{context}} placeholder. If no text was typed before the slash, {{context}} is replaced with an empty string.
- Type
/in the chat input to open the command picker - Click the + New button in the picker footer
- Fill in the command editor form:
- Name: Auto-slugified for the folder name
- Description: Shown in the picker
- Icon: Select from the icon grid
- Modes: Toggle which modes the command is visible in (empty = all)
-
Prompt template: The message to submit, with optional
{{context}}
- Click Create
- Hover over a user command in the picker to reveal edit and delete buttons
- Editing opens the same form pre-filled with current values
- Deleting removes the command folder from the workspace
workspace-docs/
├── commands/
│ └── custom/
│ ├── quick-review/
│ │ └── COMMAND.md
│ └── explain-code/
│ └── COMMAND.md
└── ... other workspace files
agent_go/pkg/commands/
├── types.go # Command, CommandFrontmatter, request/response types
├── parser.go # Parse/serialize YAML frontmatter + markdown
└── discovery.go # CRUD operations via workspace API
type CommandFrontmatter struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
Modes []string `yaml:"modes,omitempty" json:"modes,omitempty"`
}
type Command struct {
Frontmatter CommandFrontmatter `json:"frontmatter"`
Content string `json:"content"` // Prompt template after frontmatter
FolderName string `json:"folder_name"`
FilePath string `json:"file_path"`
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/commands |
List all user-defined commands |
| POST | /api/commands |
Create a new command (body: { name, content }) |
| GET | /api/commands/{name} |
Get a specific command |
| PUT | /api/commands/{name} |
Update a command's content |
| DELETE | /api/commands/{name} |
Delete a command folder |
The commands package reuses skills.WorkspaceAPIClient for all file operations (list, read, write, create folder, delete folder). Commands are stored under commands/custom/ in the workspace.
frontend/src/commands/
├── types.ts # CommandDefinition, CommandContext interfaces
├── builtin-commands.tsx # Hardcoded builtins (/pulse only)
├── agentworksProductData.ts # Fetch agentworks product.yaml commands
├── agentworksProductCommands.tsx # Adapter: yaml entries to definitions
├── pulse-review-focus.ts # Review focus options shared by picker and adapter
├── registry.ts # getCommands(), findCommand(), setUserCommands(), setProductCommands()
├── user-commands.ts # Load user commands from API, icon mapping
└── index.ts # Re-exports
frontend/src/components/
├── CommandSelectionDialog.tsx # Slash-command picker
└── commands/CommandEditorDialog.tsx # Create/edit command modal form
frontend/src/api/commands.ts # CRUD API client
frontend/src/types/commands.ts # Backend model types
// frontend/src/api/commands.ts
export const commandsApi = {
listCommands(): Promise<ListCommandsResponse>
getCommand(name: string): Promise<UserCommand>
createCommand(request: CreateCommandRequest): Promise<UserCommand>
updateCommand(name: string, request: UpdateCommandRequest): Promise<UserCommand>
deleteCommand(name: string): Promise<void>
}- When the command picker dialog opens,
loadAndRegisterUserCommands()is called - This fetches
GET /api/commandsfrom the backend - Each
UserCommandis converted to aCommandDefinitionwith:- Icon mapped from string name to lucide-react component
-
executefunction that substitutes{{context}}and callsonSubmit()
- User commands are registered via
setUserCommands()and merged with built-in commands
-
CommandSelectionDialog.tsx: UsesgetCommands(mode, workshopMode)from the registry. Shows edit/delete buttons on hover for user commands. Has aNewbutton in the footer. -
ChatInput.tsx: UsesfindCommand(name)to look up and execute commands. Builds aCommandContextfrom component state and passes it tocmd.execute(ctx). Manages the command editor dialog state.
-
Discover: Type
/in chat input → command picker appears with all available commands - Filter: Continue typing to search commands by name or description
- Execute: Select a command → it runs immediately (opens a dialog, submits a message, toggles a setting, etc.)
-
Create: Click
+ New→ fill in the editor form → command is saved to workspace and immediately available - Manage: Hover over user commands to edit or delete them
Auto-synced from docs/ on main. Edit there, not here.