fix: add OpenCode slash command discovery and execution support#568
fix: add OpenCode slash command discovery and execution support#568pedramamini merged 8 commits intoRunMaestro:rcfrom
Conversation
OpenCode slash commands (both built-in and custom) were silently ignored because the capability flag was false and discovery was hard-gated to Claude Code only. This adds disk-based discovery for OpenCode commands from .opencode/commands/*.md, ~/.config/opencode/commands/*.md, and opencode.json config files, and makes getSlashCommandDescription() agent-aware to provide correct descriptions for each agent's commands. Closes RunMaestro#552 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR enables OpenCode slash command discovery by reading from disk ( Key changes:
One issue to address: When Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Session becomes active] --> B{toolType?}
B -- claude-code --> C[fetchCustomCommands\nmaestro.claude.getCommands]
B -- claude-code --> D[discoverAgentCommands\nspawn claude --print /help]
B -- opencode --> E[discoverAgentCommands\nagents:discoverSlashCommands IPC]
B -- other --> Z[Return — no slash commands]
E --> F[discoverOpenCodeSlashCommands cwd]
F --> G[Seed Set with\nOPENCODE_BUILTIN_COMMANDS]
G --> H[Promise.all: 4 sources]
H --> I[readdir .opencode/commands/*.md]
H --> J[readdir XDG_CONFIG_HOME/opencode/commands/*.md]
H --> K[readFile cwd/opencode.json → command keys]
H --> L[readFile XDG_CONFIG_HOME/opencode/opencode.json → command keys]
I -- ENOENT --> M[log debug, skip]
J -- ENOENT --> M
K -- ENOENT --> M
L -- ENOENT --> M
I -- other error --> N[throw → Promise.all rejects]
K -- bad JSON --> O[log warn, skip]
H --> P[Return Array.from commands Set]
P --> Q[Map commands to objects with\ngetSlashCommandDescription cmd, toolType]
Q --> R[mergeCommands into session.agentCommands]
C --> R
D --> R
Last reviewed commit: "fix: handle malforme..." |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughEnables OpenCode native slash commands: flips opencode capability to support slash commands, adds discovery of built-in/project/global/opencode.json commands in the IPC handler, surfaces agent-aware command descriptions in the renderer, and expands tests for these flows. Changes
Sequence Diagram(s)sequenceDiagram
participant Renderer as Renderer (UI)
participant IPC as IPC Handler (agents:discoverSlashCommands)
participant FS as Filesystem (project/global .opencode + opencode.json)
participant Session as Session (agentCommands)
Renderer->>IPC: Request slash commands for agentId "opencode"
IPC->>IPC: discoverOpenCodeSlashCommands(cwd)
IPC->>FS: Read OPENCODE_BUILTIN_COMMANDS
FS-->>IPC: Built-in commands
IPC->>FS: Read project .opencode/commands/*.md
FS-->>IPC: Project command file names
IPC->>FS: Read project opencode.json
FS-->>IPC: Project opencode.json content
IPC->>FS: Read global config (.opencode or base/opencode)
FS-->>IPC: Global command files / opencode.json
IPC->>IPC: Consolidate unique command names
IPC-->>Renderer: Return merged command array
Renderer->>Session: Populate agentCommands with descriptions (agent-aware)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The test expected OpenCode to return null from discoverSlashCommands, but it now correctly returns built-in commands. Updated to test Codex (which genuinely has no slash commands) and added a dedicated test for OpenCode built-in command discovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
typeof [] === 'object' is true in JS, so an array value for the "command" property in opencode.json would produce bogus /0, /1 slash command entries. Add !Array.isArray() guard to both project-level and global config parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/hooks/wizard/useWizardHandlers.ts (1)
178-268:⚠️ Potential issue | 🟠 MajorOpenCode sessions still never run slash-command discovery here.
Line 183 returns unless the active session is Claude Code, so this effect never calls
agents.discoverSlashCommands('opencode', ...). WithsupportsSlashCommandsnow enabled, OpenCode sessions will still keep an emptyagentCommandslist and won't show the new autocomplete entries.🛠️ Suggested wiring fix
- if (currentSession.toolType !== 'claude-code') return; + if ( + currentSession.toolType !== 'claude-code' && + currentSession.toolType !== 'opencode' + ) { + return; + } if (currentSession.agentCommands && currentSession.agentCommands.length > 0) return; @@ - fetchCustomCommands(); + if (currentSession.toolType === 'claude-code') { + fetchCustomCommands(); + } discoverAgentCommands();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/hooks/wizard/useWizardHandlers.ts` around lines 178 - 268, The effect currently bails out unless currentSession.toolType === 'claude-code', preventing agents.discoverSlashCommands from running for 'opencode' sessions; update the early-return so it allows any tool that supports slash commands (e.g., replace the strict 'claude-code' check with a call to the supportsSlashCommands predicate or include 'opencode') so discoverAgentCommands (which calls maestro.agents.discoverSlashCommands) runs for those sessions and populates s.agentCommands; adjust the condition around currentSession.toolType in useEffect in useWizardHandlers.ts accordingly.
🧹 Nitpick comments (1)
src/__tests__/main/ipc/handlers/agents.test.ts (1)
1094-1108: This only covers the built-in happy path for OpenCode.The new risk is in
.opencode/commands/*.mdandopencode.jsonparsing, but this assertion only proves the built-in set is seeded. A regression in the disk/config discovery logic—or in theArray.isArrayguard oncommand—would still leave this suite green.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/__tests__/main/ipc/handlers/agents.test.ts` around lines 1094 - 1108, The test currently only verifies built-in OpenCode commands; add assertions and setup to exercise the on-disk/config discovery paths so regressions in parsing .opencode/commands/*.md and opencode.json (and the Array.isArray guard on `command`) are caught: mock the filesystem reads (or the helper that loads .opencode files) to return a mix of valid and invalid command definitions and a sample opencode.json, call the same handler retrieved via handlers.get('agents:discoverSlashCommands') after mocking mockAgentDetector.getAgent to return the opencode agent, and assert the final result includes commands discovered from disk/config and that non-array/invalid `command` entries are ignored (or handled) as expected. Ensure you reference and exercise the command-parsing code path used by agents:discoverSlashCommands so the disk/config logic is validated in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/ipc/handlers/agents.ts`:
- Around line 50-116: The catch blocks around reading project/global command
directories and project/global opencode.json currently swallow all errors;
change each catch to only suppress "file not found" (error.code === 'ENOENT')
while rethrowing other errors (including permission errors and JSON
parse/SyntaxError) so they propagate to IPC error handling/Sentry. Locate the
four try/catch blocks that reference projectCommandsDir, globalCommandsDir,
projectConfigPath, and globalConfigPath (they update the commands Set and use
logger and LOG_CONTEXT) and replace the generic catch handlers with logic: if
error?.code === 'ENOENT' then logger.debug(...) as before, else throw error.
In `@src/renderer/constants/app.ts`:
- Around line 120-129: The loop that searches AGENT_BUILTIN_COMMANDS for cmdName
should only run when agentId is not provided; currently if an agentId is passed
but lacks cmdName you still fall back to another agent's command. Modify the
logic around AGENT_BUILTIN_COMMANDS, agentId, and cmdName so that after checking
AGENT_BUILTIN_COMMANDS[agentId]?.[cmdName] you only iterate
Object.values(AGENT_BUILTIN_COMMANDS) when agentId is falsy (i.e., guard the
fallback with if (!agentId) or return undefined when an agentId was supplied and
its map does not contain cmdName).
---
Outside diff comments:
In `@src/renderer/hooks/wizard/useWizardHandlers.ts`:
- Around line 178-268: The effect currently bails out unless
currentSession.toolType === 'claude-code', preventing
agents.discoverSlashCommands from running for 'opencode' sessions; update the
early-return so it allows any tool that supports slash commands (e.g., replace
the strict 'claude-code' check with a call to the supportsSlashCommands
predicate or include 'opencode') so discoverAgentCommands (which calls
maestro.agents.discoverSlashCommands) runs for those sessions and populates
s.agentCommands; adjust the condition around currentSession.toolType in
useEffect in useWizardHandlers.ts accordingly.
---
Nitpick comments:
In `@src/__tests__/main/ipc/handlers/agents.test.ts`:
- Around line 1094-1108: The test currently only verifies built-in OpenCode
commands; add assertions and setup to exercise the on-disk/config discovery
paths so regressions in parsing .opencode/commands/*.md and opencode.json (and
the Array.isArray guard on `command`) are caught: mock the filesystem reads (or
the helper that loads .opencode files) to return a mix of valid and invalid
command definitions and a sample opencode.json, call the same handler retrieved
via handlers.get('agents:discoverSlashCommands') after mocking
mockAgentDetector.getAgent to return the opencode agent, and assert the final
result includes commands discovered from disk/config and that non-array/invalid
`command` entries are ignored (or handled) as expected. Ensure you reference and
exercise the command-parsing code path used by agents:discoverSlashCommands so
the disk/config logic is validated in the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2ed8ee64-8158-4c7f-b0db-2165b17458dc
📒 Files selected for processing (6)
src/__tests__/main/ipc/handlers/agents.test.tssrc/main/agents/capabilities.tssrc/main/ipc/handlers/agents.tssrc/renderer/constants/app.tssrc/renderer/hooks/agent/useAgentListeners.tssrc/renderer/hooks/wizard/useWizardHandlers.ts
- Convert discoverOpenCodeSlashCommands to async using fs.promises - Respect XDG_CONFIG_HOME for global OpenCode config paths - Only suppress ENOENT errors; rethrow permission/parse errors to Sentry - Fix cross-agent description leak in getSlashCommandDescription - Wire OpenCode sessions into slash command discovery in useWizardHandlers - Add tests for disk/config discovery, array guard, and error propagation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai re-review please |
|
🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main/ipc/handlers/agents.ts (1)
32-33: Potential for silent drift with renderer-side command list.
OPENCODE_BUILTIN_COMMANDSis defined here as a string array, whilesrc/renderer/constants/app.ts(line 94-102) definesOPENCODE_BUILTIN_COMMANDSas aRecord<string, string>with descriptions. If commands are added or removed in one location but not the other, the lists will silently diverge.Consider extracting the canonical command list to a shared location (e.g.,
src/shared/constants.ts) and deriving both usages from it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/ipc/handlers/agents.ts` around lines 32 - 33, OPENCODE_BUILTIN_COMMANDS is duplicated in different shapes which can silently diverge; extract a single canonical source (e.g., a shared constants module exporting a canonicalCommands array) and derive both usages from it: export the canonicalCommands (string[]) and then derive OPENCODE_BUILTIN_COMMANDS (string[]) and the renderer's OPENCODE_BUILTIN_COMMANDS record (mapping to descriptions) by importing and transforming the canonical list; update the references in the handler that uses OPENCODE_BUILTIN_COMMANDS and the renderer constant to import from the shared module and remove the local hardcoded list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/main/ipc/handlers/agents.ts`:
- Around line 32-33: OPENCODE_BUILTIN_COMMANDS is duplicated in different shapes
which can silently diverge; extract a single canonical source (e.g., a shared
constants module exporting a canonicalCommands array) and derive both usages
from it: export the canonicalCommands (string[]) and then derive
OPENCODE_BUILTIN_COMMANDS (string[]) and the renderer's
OPENCODE_BUILTIN_COMMANDS record (mapping to descriptions) by importing and
transforming the canonical list; update the references in the handler that uses
OPENCODE_BUILTIN_COMMANDS and the renderer constant to import from the shared
module and remove the local hardcoded list.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3cfac1ae-2b88-4a40-9301-f648799f32a3
📒 Files selected for processing (6)
src/__tests__/main/ipc/handlers/agents.test.tssrc/main/agents/capabilities.tssrc/main/ipc/handlers/agents.tssrc/renderer/constants/app.tssrc/renderer/hooks/agent/useAgentListeners.tssrc/renderer/hooks/wizard/useWizardHandlers.ts
…very SyntaxError from JSON.parse has no .code property, so it bypassed the ENOENT check and rejected the entire Promise.all — wiping out built-in commands. Split the try/catch so file-not-found and parse errors are handled independently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
OpenCode command still not being picked up, converting this to draft to get further work done. |
Remove built-in commands without prompt files (init, review, undo, etc.) since they're TUI-only and can't be executed in batch mode. Fix the discovery-to-execution pipeline: preload type now reflects object returns from OpenCode, useWizardHandlers extracts prompt from discovered commands, agentStore falls back to agentCommands for execution, and the onSlashCommands listener preserves prompt fields when merging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The PR code will now pickup custom slash commands from OpenCode from their .md files. |
|
@coderabbitai re-review please |
|
🧠 Learnings used✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/renderer/constants/app.ts (1)
94-102: Consider adding a sync mechanism or shared constant for built-in command names.The
OPENCODE_BUILTIN_COMMANDSkeys here must stay synchronized with theOPENCODE_BUILTIN_COMMANDSarray insrc/main/ipc/handlers/agents.ts. Currently these are independently maintained duplicates. If a command is added/removed in one location but not the other, descriptions will be missing or stale.A shared constant (e.g., in
src/shared/) or a single source of truth could prevent drift. This is not blocking, but worth tracking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/constants/app.ts` around lines 94 - 102, The OPENCODE_BUILTIN_COMMANDS object in src/renderer/constants/app.ts is duplicated by an OPENCODE_BUILTIN_COMMANDS array in src/main/ipc/handlers/agents.ts; extract a single source of truth (e.g., create a new module like src/shared/opencode.ts) that exports the command map (export const OPENCODE_BUILTIN_COMMANDS: Record<string,string>) and any derived lists (export const OPENCODE_BUILTIN_COMMAND_NAMES = Object.keys(OPENCODE_BUILTIN_COMMANDS)), then update both renderer and main to import those exports instead of maintaining independent copies so keys and descriptions stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/ipc/handlers/agents.ts`:
- Around line 99-100: Update the code that builds the list of config paths
passed to addCommandsFromConfig so it becomes platform-aware and honors
OPENCODE_CONFIG overrides: detect OPENCODE_CONFIG environment variable and use
it first if set, then probe platform-specific locations (on POSIX:
project-root/opencode.json, ~/.opencode.json, ~/.config/opencode/opencode.json;
on Windows: %LOCALAPPDATA%/opencode/.opencode.json,
%USERPROFILE%/.opencode.json, project-root/opencode.json) and include the global
config fallback, using the same resolution logic as path-prober.ts; replace the
current hardcoded path.join calls with this probe function and ensure
addCommandsFromConfig is called for each resolved path in precedence order.
---
Nitpick comments:
In `@src/renderer/constants/app.ts`:
- Around line 94-102: The OPENCODE_BUILTIN_COMMANDS object in
src/renderer/constants/app.ts is duplicated by an OPENCODE_BUILTIN_COMMANDS
array in src/main/ipc/handlers/agents.ts; extract a single source of truth
(e.g., create a new module like src/shared/opencode.ts) that exports the command
map (export const OPENCODE_BUILTIN_COMMANDS: Record<string,string>) and any
derived lists (export const OPENCODE_BUILTIN_COMMAND_NAMES =
Object.keys(OPENCODE_BUILTIN_COMMANDS)), then update both renderer and main to
import those exports instead of maintaining independent copies so keys and
descriptions stay in sync.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 27151de2-08b4-40c3-8a2d-d725b2066ac9
📒 Files selected for processing (6)
src/__tests__/main/ipc/handlers/agents.test.tssrc/main/agents/capabilities.tssrc/main/ipc/handlers/agents.tssrc/renderer/constants/app.tssrc/renderer/hooks/agent/useAgentListeners.tssrc/renderer/hooks/wizard/useWizardHandlers.ts
…ENCODE_CONFIG Address PR review: config paths are now resolved per-platform (POSIX vs Windows) and the OPENCODE_CONFIG env var is checked first as an explicit override. Also adds ~/.opencode.json to the probe list and %LOCALAPPDATA%/opencode/ on Windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me pull the relevant code to review the cross-platform changes. 🏁 Script executed: Length of output: 3493 Let me check the 🏁 Script executed: Length of output: 5366
Gap: Windows global commands directory is never probed The config JSON resolution (lines 147–162) is correctly platform-aware and probes // Always runs on ALL platforms — including Windows:
await addCommandsFromDir(path.join(cwd, '.opencode', 'commands')); // ✅ correct (all platforms)
await addCommandsFromDir(path.join(opencodeHome, 'commands')); // ✅ ~/.opencode/commands
await addCommandsFromDir(path.join(globalConfigBase, 'opencode', 'commands')); // ⚠️ ~/.config/opencode/commands — POSIX onlyOn Windows, OpenCode stores global commands in Suggested fix — mirror the same branching pattern used for config paths: await addCommandsFromDir(path.join(cwd, '.opencode', 'commands'));
await addCommandsFromDir(path.join(opencodeHome, 'commands'));
if (isWindows()) {
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
await addCommandsFromDir(path.join(localAppData, 'opencode', 'commands'));
} else {
await addCommandsFromDir(path.join(globalConfigBase, 'opencode', 'commands'));
}This keeps the commands directory discovery consistent with the already-correct config JSON branching logic and ensures Windows users' global 🧠 Learnings used |
On Windows, probe %LOCALAPPDATA%/opencode/commands/ instead of the POSIX-only XDG path that always hits ENOENT. Matches the same platform-aware logic already applied to config file resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
.opencode/commands/*.md,~/.config/opencode/commands/*.md, andopencode.jsonconfig)supportsSlashCommands: truefor OpenCode agent so commands appear in autocompletegetSlashCommandDescription()agent-aware to provide correct descriptions per agentCloses #552
Changes
src/main/agents/capabilities.tssupportsSlashCommandstotruefor opencodesrc/main/ipc/handlers/agents.tsdiscoverOpenCodeSlashCommands()— disk-based discovery from 3 sources (built-in, project-local, global + config)src/renderer/constants/app.tsOPENCODE_BUILTIN_COMMANDSdescriptions, makegetSlashCommandDescription()agent-awaresrc/renderer/hooks/agent/useAgentListeners.tstoolTypetogetSlashCommandDescription()src/renderer/hooks/wizard/useWizardHandlers.tstoolTypetogetSlashCommandDescription()How it works
Unlike Claude Code (which discovers commands via
system/initJSON event), OpenCode stores commands on disk:init,review,undo,redo,share,help,models.opencode/commands/<name>.mdfiles~/.config/opencode/commands/<name>.mdfilesopencode.json→commandpropertyThe discovery function reads all three sources and returns a unified command list, matching Claude Code's end-to-end pipeline (discover → autocomplete → execute).
Test plan
.opencode/commands/test.mdfile and verify it's discovered🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Tests