Backlog source: docs/bugs.md
Implemented for discovery and backend-owned execution. GET
/api/sessions/{id}/agent-commands now serves:
.claude/commands/*.md prompt templates from the session workdirThe slash palette shows those commands alongside the existing session-control
commands, including the catalog-gated Codex /fast Standard/Fast selector.
Codex /mcp is also shown there, but it is a dedicated client-side status
surface backed by mcpServerStatus/list, not an agent command and not a prompt
sent to the model.
Native Claude commands are sent as slash prompts such as /review,
while markdown templates are resolved through POST
/api/sessions/{id}/agent-commands/{name}/resolve. The frontend passes
arguments and optional note; the backend applies $ARGUMENTS, appends any
note as a standard user-note block, and returns the resolved prompt plus any
trusted delegation defaults. Regular session sends and delegated sends use the
same resolver.
See slash-commands.md for the existing session-control implementation.
Claude exposes two useful command surfaces:
.claude/commands/*.md/review,
/release-notes, or /security-reviewTermAl supports filesystem prompt templates for local sessions and merges live native-command metadata for Claude sessions when available. This brief remains useful as the design record for that work.
/ in the composer and see agent commands alongside session controls..md content sent as the prompt./fix-bug 3)..md files directly).AgentSessionPanel.tsx uses a static SLASH_COMMANDS array
with hardcoded SlashCommandId values ("model" | "mode" | "sandbox" | ...)."command" (expands text) or "choice" (applies a setting).
Agent commands use the third kind: "agent-command"..claude/commands..md) as the command
name. When YAML frontmatter is present, description: and argument-hint:
populate command metadata and TermAl strips the frontmatter before sending the
prompt body; otherwise the first non-empty body line becomes the description.GET /api/sessions/{id}/agent-commands
Response:
{
"commands": [
{
"name": "review-code",
"description": "Review staged and unstaged changes using multiple specialized reviewers.",
"content": "Review staged and unstaged changes using...\n\n## Step 1: ...",
"source": ".claude/commands/review-code.md"
},
{
"name": "fix-bug",
"description": "Fix a bug from docs/bugs.md by number.",
"content": "Fix a bug from `docs/bugs.md`...\n\n$ARGUMENTS\n...",
"source": ".claude/commands/fix-bug.md"
}
]
}
Implementation:
workdir from SessionRecord.{workdir}/.claude/commands/*.md on each request./review
and runtime-backed project commands dispatch natively when available.Target contract:
POST /api/sessions/{id}/agent-commands/{name}/resolve
Request:
{
"arguments": "1024",
"note": "Please add integration tests for Connectivity class.",
"cwd": "C:\\github\\Personal\\TermAl",
"intent": "delegate"
}
intent is "send" for a normal session turn and "delegate" for child-session
delegation. The resolver must use the same template expansion rules for both
intents, but may return different execution defaults for delegation.
cwd is optional and delegation-only. It lets MCP-based delegation resolve a
slash command against the intended child session working directory before the
child exists. Requests with cwd and intent: "send" are rejected. For
project-scoped sessions the canonicalized cwd must stay inside the local
project root; non-project sessions may resolve against any local directory the
backend process can read. Remote-backed projects return NOT_IMPLEMENTED in
Phase 1. The value is capped at 4,096 Unicode code points. Callers should pass
absolute paths; relative paths follow the server-side session workdir resolution
rules.
Response:
{
"name": "fix-bug",
"source": ".claude/commands/fix-bug.md",
"kind": "promptTemplate",
"visiblePrompt": "/fix-bug 1024",
"expandedPrompt": "Fix a bug from docs/bugs.md...\n\n1024\n\n## Additional User Note\n\nPlease add integration tests for Connectivity class.",
"title": "Fix bug 1024"
}
Delegating a prompt-template command with trusted command-owned defaults returns
delegation only for intent: "delegate". This is the trusted-source response
shape; project-local .claude/commands/*.md templates are not trusted today and
do not return delegation defaults:
{
"name": "review-code",
"source": ".claude/commands/review-code.md",
"kind": "promptTemplate",
"visiblePrompt": "/review-code",
"expandedPrompt": "Review staged and unstaged changes...",
"title": "Review staged and unstaged changes using multiple specialized reviewers.",
"delegation": {
"title": "Review staged and unstaged changes using multiple specialized reviewers.",
"mode": "reviewer",
"writePolicy": { "kind": "isolatedWorktree", "ownedPaths": [] }
}
}
Resolution rules:
arguments and optional note as separate fields. The
backend should not infer command-specific structure from a single free-form
string unless command metadata declares that structure.arguments and note are trimmed and each capped at 65,536 bytes before
template interpolation or note appending.$ARGUMENTS in prompt templates is replaced with arguments exactly after
trimming only outer whitespace.note is never substituted into the template. If present after trimming outer
whitespace, append it to the resolved prompt as:
## Additional User Note
<note text>
note is empty or omitted, the note block is omitted.kind: "nativeSlash") resolve to a literal
visiblePrompt such as /review. If a native runtime cannot accept appended
notes, the resolver must either reject note with a validation error or
convert the request to a prompt-template path that TermAl owns.cwd, when present, changes command discovery and resolver metadata reads to
that directory. This is only valid with intent: "delegate" and is bounded by
the local project root when the parent session is project-scoped.Command templates and future SKILL.md files may declare TermAl execution
metadata under metadata.termal. This follows the Claude skill model: YAML
frontmatter is the always-loaded discovery layer, while the Markdown body remains
the prompt/instruction payload. TermAl strips recognized frontmatter before
sending the template to an agent and uses description: as the command palette
description when present.
TermAl parses prompt-template command frontmatter for resolver metadata today.
Project-local .claude/commands/*.md metadata may drive title generation after
passing the source/name gate, but delegation defaults that affect mode or write
policy are ignored. No production command source is marked trusted yet; future
TermAl-owned command or SKILL.md support should reuse the same
metadata.termal shape and set the trusted-source marker only for those
TermAl-owned files.
Metadata contract:
---
name: review-code
description: Review staged and unstaged changes using multiple specialized reviewers.
metadata:
termal:
title:
strategy: default
delegation:
enabled: true
mode: reviewer
writePolicy:
kind: readOnly
---
Title strategies:
default: use the command description or visible prompt.prefixFirstArgument: use <prefix> <first argument> when an argument is
present, otherwise fall back to default.Delegation metadata:
enabled: true allows the resolver to return delegation
defaults for intent: "delegate".mode currently accepts reviewer or explorer; worker remains blocked
until write-enabled worker delegations are implemented.writePolicy.kind currently accepts readOnly or isolatedWorktree.
sharedWorktree remains unsupported for command metadata.Trust rules:
review-code must not inherit TermAl privileges by
name.metadata.termal must fail command resolution with a clear
validation error. It must not silently broaden permissions or fall back to a
more permissive policy. Untrusted delegation metadata is ignored rather than
applied./review-code and
/fix-bug are examples, not special cases in Rust code. Review coordinators
such as /review-changes run directly in the active writable session and
explicitly delegate only their leaf reviewers.Example user intent:
/fix-bug 1024 -- Please add integration tests for Connectivity class.
The UI can parse this into arguments: "1024" and note: "Please add
integration tests for Connectivity class.", then call the resolver. Without an
unambiguous separator or metadata, the whole tail should be sent as
arguments, with note omitted.
Extend the slash palette to support agent commands.
// New palette item kind
type SlashPaletteItem =
| { kind: "command"; ... } // existing: session control (expands text)
| { kind: "choice"; ... } // existing: setting value (applies immediately)
| { kind: "agent-command"; // new: agent slash command
key: string;
command: string; // "/review-code"
label: string; // "/review-code"
detail: string; // first line of .md file
content: string; // full .md template content for display/compatibility
hasArguments: boolean; // true if content contains $ARGUMENTS
};
// api.ts
export function fetchAgentCommands(sessionId: string): Promise<AgentCommandsResponse> {
return request<AgentCommandsResponse>(
`/api/sessions/${encodeURIComponent(sessionId)}/agent-commands`
);
}
Fetch agent commands:
Modify buildSlashPaletteState to include agent commands:
User types "/"
→ Show two sections:
┌─────────────────────────────────────┐
│ Agent Commands │
│ /review-code Review staged... │
│ /fix-bug Fix a bug from... │
│ Session Controls │
│ /model Change the model │
│ /mode Change the mode │
│ /effort Change effort │
└─────────────────────────────────────┘
User types "/rev"
→ Filter to matching commands:
┌─────────────────────────────────────┐
│ Agent Commands │
│ /review-code Review staged... │
└─────────────────────────────────────┘
When an agent command is selected:
Without arguments (hasArguments: false):
visiblePrompt and expandedPrompt through the normal session-send path.With arguments (hasArguments: true):
/fix-bug (with trailing space).3).arguments, and optional note to
the backend resolver, then send the resolved prompt.Delegation:
intent: "delegate".expandedPrompt.writePolicy, mode,
and title. React components must not hard-code command names such as
review-code to choose write policy.Claude Code’s convention:
$ARGUMENTS in the .md content is replaced with the resolver request’s
arguments field./fix-bug 3; the UI passes arguments: "3" and the
backend replaces $ARGUMENTS with 3.$ARGUMENTS is present, send with $ARGUMENTS replaced
by empty string (matches Claude Code behavior).Additional
User Note section. They are not substituted into $ARGUMENTS.Agent Commands above agent commands in the palette.Session Controls above the existing session-control commands./command-name for arg input..md files).Discovery endpoint:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/sessions/{id}/agent-commands |
Discover agent commands for session’s project |
Resolution endpoint:
| Method | Path | Purpose |
|---|---|---|
| POST | /api/sessions/{id}/agent-commands/{name}/resolve |
Resolve a command template/native command into the prompt and execution defaults for a regular send or delegation |
The discovery response may continue to include command content for display and
compatibility, but frontend execution should use the resolver as the source of
truth. This keeps command/skill parsing, $ARGUMENTS, optional notes, and
delegation policy on the backend.
AgentCommand struct to main.rs.list_agent_commands handler.GET /api/sessions/{id}/agent-commands route..claude/commands/*.md from session workdir.AgentCommand type to types.ts.fetchAgentCommands() to api.ts.SlashPaletteItem with "agent-command" kind.buildSlashPaletteState to merge agent commands into the palette.applySlashPaletteItem for "agent-command" kind.$ARGUMENTS substitution and optional
notes.writePolicy, not React hard-coding.Backend:
.md files → returns correct list..md files in the directory → ignored.Backend resolver:
$ARGUMENTS with the request arguments.## Additional User Note only when note is non-empty.Frontend:
/ in the composer shows agent commands from .claude/commands/ alongside
session controls./review-code resolves through the backend and sends the resolved
prompt to the active session./fix-bug expands to /fix-bug in the composer; typing 3 and pressing
Enter resolves with arguments: "3" and sends the resolved prompt.Additional User Note block without changing
$ARGUMENTS..md file to .claude/commands/ and clicking refresh shows the new command./model, /mode, /effort) continue to work
unchanged.