Skip to content

OpenCode skill slash commands: wrong execution path (user message swallowed, double prompt injection, dead _execute_skill_command) #339

Description

@Leoyzen

Summary

When a skill is triggered via a slash command (e.g. /lodestone) in the OpenCode protocol server, the execution path diverges completely from the normal prompt path. This causes three user-visible problems.

Symptoms

  1. TUI shows Loading skill: lodestone (skill://lodestone) as the AI's reply — the skill_bridge's ctx.print(...) output is captured into the assistant message's TextPart, so the frontend renders it as if the AI said it.

  2. The user's first message is swallowed_execute_slashed_command (in src/agentpool_server/opencode_server/routes/session_routes.py) only creates an assistant_message; it never creates a role="user" message. The user's /lodestone <args> input never becomes a user message in session history, so the TUI never displays it.

  3. Multiple mechanisms break because the path is different — skill commands route through session_pool.run_stream(...) instead of the normal _route_messagesend_message_consume_run path:

    • Double prompt injection: skill_bridge.execute_skill injects the full <skill-instruction> prompt into staged_content, AND _execute_slashed_command assembles a second agent_prompt ("用户执行了命令 '...' 并说: ...") passed to run_stream. The model receives both, with the second being hardcoded Chinese template text.
    • Broken parent_id chain: assistant message has parent_id="" instead of linking to a user message.
    • Message ID mismatch: run_stream(..., message_id=assistant_msg_id) passes the assistant ID where the normal path expects a user message ID.

Root Cause

Skill commands are registered as generic slashed commands (OpenCodeSkillBridgeCommandStore) and dispatched through _execute_slashed_command, which was designed for simple commands (like /help) that print text. A skill command needs the full prompt lifecycle: user message creation → instruction injection → agent processing → assistant reply. The generic command path lacks user-message creation entirely.

_execute_skill_command (line ~343) — which would have the correct behavior (creates user message, links parent_id) — is dead code: skill_cmd = None is never assigned, so it always raises 404.

Related Context

Expected Behavior

Skill commands should route through the same EventBus-only path as normal prompts:

  1. Create and broadcast a role="user" message (user's input visible in TUI).
  2. Inject skill instructions via staged_content (already done by skill_bridge).
  3. Route via route_messagesend_message_consume_run (EventBus-only, exactly-once events).
  4. Do NOT use session_pool.run_stream() for skill commands.
  5. Fix or remove the dead _execute_skill_command path.

Acceptance Criteria

  • /lodestone <args> shows the user's message in the TUI
  • Model receives skill instructions exactly once (no double prompt injection)
  • Skill command output flows through EventBus-only consumption
  • No Loading skill: ... text rendered as AI reply (or moved to a proper notification part)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions