Skip to content

feat: support plugin slash commands#1204

Merged
wbxl2000 merged 21 commits into
mainfrom
feat/plugin-commands
Jun 30, 2026
Merged

feat: support plugin slash commands#1204
wbxl2000 merged 21 commits into
mainfrom
feat/plugin-commands

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No related issue. This is a follow-up to the plugin-hooks work: it lets plugins contribute slash commands, which is one of the pieces needed to run Claude-Code-style plugins (for example vercel/vercel-plugin) in Kimi Code.

Problem

Plugins can already provide skills, hooks, and MCP servers, but they cannot provide slash commands. A Claude-Code-style plugin that ships a commands/ directory (for example /vercel-plugin:deploy) has no way to register those commands in Kimi Code, so users cannot invoke them.

What changed

  • Plugins can now declare a commands field in kimi.plugin.json (a path or list of paths to .md files or a directory of .md files, scoped to the plugin root).
  • Each command file is parsed for frontmatter (name, description) plus a markdown body used as the prompt template. name falls back to the file name; description falls back to the first body line.
  • Commands are registered as <plugin>:<command> and show up in the / autocomplete. Invoking one activates it server-side (mirroring skill activation): the agent receives the body with $ARGUMENTS expanded (appending them when the body has no placeholder), while the TUI renders a compact ▶ /plugin:command card rather than expanding the body into the chat.
  • The plugin summary now carries a commandCount.

This mirrors the existing plugin skills flow end to end (manifest → manager → session → RPC → SDK → TUI) and reuses the skill frontmatter parser.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Plugin-author documentation for the new commands field is not included yet and can follow in a separate docs PR.

@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9a90d81

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@9a90d81
npx https://pkg.pr.new/@moonshot-ai/kimi-code@9a90d81

commit: 9a90d81

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0b3c62e66

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +362 to +364
return entries
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
.map((entry) => path.join(dir, entry.name))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recurse into nested plugin command directories

When a plugin declares commands: "./commands", this only registers .md files that are direct children of that directory. Claude-style command trees support subdirectories for namespaced commands (for example commands/frontend/component.md), so those valid plugin commands are silently omitted from autocomplete and cannot be invoked. Please walk the directory recursively and preserve the relative path namespace when building command names.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 090d64efaf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

export function buildPluginSlashCommands(defs: readonly PluginCommandDef[]): PluginSlashCommands {
const commandMap = new Map<string, string>();
const commands = defs.map((def) => {
const commandName = pluginCommandName(def.pluginId, def.name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support nested plugin command names in dispatch

When a command comes from a nested file, plugin discovery preserves names like frontend/component, so this registers /my-plugin:frontend/component; however parseSlashInput returns null for any slash-command name containing /, causing the autocomplete suggestion to fall through as a normal user message instead of activating the plugin command. Either avoid registering slash-containing names here or relax/route the parser for plugin command namespaces.

Useful? React with 👍 / 👎.

Comment on lines +219 to +224
async activatePluginCommand({
agentId,
...payload
}: AgentScopedPayload<ActivatePluginCommandPayload>) {
await (await this.getAgent(agentId)).activatePluginCommand(payload);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update prompt metadata for plugin command turns

For main-agent plugin commands, this starts a prompt but does not call updatePromptMetadata, unlike both normal prompts and skill activations. If the first or latest user action is a plugin command, the session state keeps an empty/stale lastPrompt and title, so session lists that exclude empty sessions and the picker’s last-prompt display can treat an active session as empty or outdated.

Useful? React with 👍 / 👎.

Comment on lines +254 to +255
case 'plugin_command':
return message.origin.trigger === 'user-slash';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replay plugin commands as command cards

This makes plugin_command origins count as replay user turns, but SessionReplayRenderer.renderUserMessage still only projects skill_activation origins before falling through to a plain user bubble. After resuming a session, plugin command turns therefore show the expanded command body rather than the intended /plugin:command card and args, making the transcript inconsistent and obscuring what the user actually invoked.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4171c4eab3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

commandArgs: origin.commandArgs,
trigger: origin.trigger,
});
this.turn.prompt([{ type: 'text', text: expanded }], origin);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make plugin commands undoable

When a plugin command is submitted, this records the turn with origin.kind === 'plugin_command', but the undo logic still only treats plain user prompts and user-slash skill activations as real user prompts. In the scenario where a user runs /my-plugin:deploy and then tries /undo, the active-context availability is 0 and core undo also will not count that command, so the command and response cannot be undone. Please include plugin-command origins in the undo anchor/user-prompt checks.

Useful? React with 👍 / 👎.

backgroundTaskTranscriptedTerminal: Set<string> = new Set();

renderedSkillActivationIds: Set<string> = new Set();
renderedPluginCommandActivationIds: Set<string> = new Set();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Clear plugin-command render IDs on session reset

This rendered-ID set is session runtime state, but resetRuntimeState() clears renderedSkillActivationIds without clearing renderedPluginCommandActivationIds. If a user invokes a plugin command, switches away, and then switches back/replays the same session, replay sees the previous activation ID and skips rendering that command card, leaving the transcript missing the invocation. Clear it alongside the skill activation set.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df9b716c3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +23 to +29
export interface PluginCommandOrigin {
readonly kind: 'plugin_command';
readonly activationId: string;
readonly pluginId: string;
readonly commandName: string;
readonly commandArgs?: string | undefined;
readonly trigger: 'user-slash';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate plugin_command to turn projectors

Adding plugin_command as a new user-prompt origin also needs the consumers that mirror user-turn detection to be updated. In apps/kimi-web/src/composables/messagesToTurns.ts:382-388, only user and skill_activation are displayable, so a session containing a plugin slash command will hide that user turn in the web UI; apps/vis/server/src/lib/context-projector.ts:582-587 has the same stale mirror and will not count plugin commands as undo anchors in projections. This affects any session created from /plugin:command and then viewed through web/vis.

Useful? React with 👍 / 👎.

Comment on lines +207 to +210
const session = host.session;
if (session === undefined) {
host.showError(LLM_NOT_SET_MESSAGE);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a model before activating plugin commands

When a session exists but no model is selected, this branch still calls activatePluginCommand; the normal prompt path and the skill branch both guard appState.model.trim().length === 0 first and show LLM_NOT_SET_MESSAGE. For the same no-model state, a plugin slash command now starts a session request and lets core fail later instead of being rejected immediately, which can leave the UI in the error/recovery path for an input that should never be sent.

Useful? React with 👍 / 👎.

backgroundTaskTranscriptedTerminal: Set<string> = new Set();

renderedSkillActivationIds: Set<string> = new Set();
renderedPluginCommandActivationIds: Set<string> = new Set();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear plugin command render IDs on session reset

This new per-session dedupe set is not cleared in resetRuntimeState() (unlike renderedSkillActivationIds), so after a plugin command is rendered live, switching away from that session and back keeps its activation id in memory. Replay then hits renderPluginCommand() and skips the card, making the /plugin:command invocation disappear from the transcript for that session until the TUI process restarts.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7b1c16779

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

this.updateTerminalTitle();
}
void this.refreshSkillCommands(this.session);
void this.refreshPluginCommands(this.session);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh plugin commands after auth-created sessions

This new plugin-command cache is refreshed in the normal startup/switch paths, but I checked AuthFlowController and its login-created session path still only calls refreshSkillCommands(...) after createSession, and logout only clears skills. In the first session created immediately after an OAuth login, pluginCommandMap therefore stays empty, so /plugin:command falls through as a normal prompt until the user starts/reloads another session; after logout the autocomplete can also keep stale plugin commands. Please wire refreshPluginCommands(session)/refreshPluginCommands() through the auth flow wherever skills are refreshed.

Useful? React with 👍 / 👎.

Comment on lines +706 to +707
pluginCommand: isPluginCommand
? { pluginId: origin.pluginId!, commandName: origin.commandName!, args: origin.commandArgs }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render plugin commands in the web chat pane

When a resumed web transcript contains a plugin_command origin, this maps the user turn text to only commandArgs and records turn.pluginCommand, but I checked ChatPane.vue and it still only special-cases turn.skillActivation; plugin-command turns fall through to the plain user text renderer. As a result /plugin:deploy with no args shows an empty user bubble, and with args shows only the args rather than the command card/identity. Add web rendering/edit handling alongside the existing skill activation branch.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 574d0ae0c6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +480 to +484
<div v-else-if="turn.pluginCommand" class="skill-act">
<div class="skill-act-head">
<span class="skill-act-arrow">▶</span>
<span>/{{ turn.pluginCommand.pluginId }}:{{ turn.pluginCommand.commandName }}</span>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render plugin commands in the desktop chat view

This adds a plugin-command card only in the mobile childBubble branch, but the desktop user-message branch below still only special-cases turn.skillActivation and otherwise renders {{ turn.text }}. Since messagesToTurns now replaces plugin-command text with only commandArgs, desktop users invoking /plugin:command see just the args, or an empty user bubble when there are no args, instead of the command identity/card that mobile gets.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17d9ca11fb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

case 'plugin_command': {
const data = entry.pluginCommandData;
if (data === undefined) return null;
return new PluginCommandComponent(data.pluginId, data.commandName, data.args);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat plugin command cards as turn boundaries

When a session is driven by plugin slash commands, these cards are mounted as PluginCommandComponents, but trimTranscriptWindow()/mergeCurrentTurnSteps() only recognize UserMessageComponent as a turn boundary. Once old plugin-command turns exceed the transcript window, the trim path can remove their TranscriptEntrys without removing the mounted components, so the TUI keeps stale cards/steps around and the transcript window no longer bounds memory for those turns. Mark plugin command cards as turn-starting components or extend the boundary check to include them.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79c6963243

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (
!(child instanceof UserMessageComponent) &&
!(child instanceof SkillActivationComponent) &&
!(child instanceof PluginCommandComponent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count plugin commands when trimming turns

When old turns are plugin slash commands, this new boundary type makes the rendered card a turn boundary, but trimTranscriptWindow() still increments boundariesToRemove only for entry.kind === 'user' && entry.turnId === undefined below. Once enough /plugin:... turns exceed the transcript window, toRemove contains plugin_command entries while boundariesToRemove stays too small (or zero), so the state entries are dropped without removing the corresponding rendered components, leaving stale transcript UI and defeating the window cap for those turns.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76a26bf34a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

entry.kind === 'user' ||
(entry.kind === 'skill_activation' && entry.skillTrigger === 'user-slash')
(entry.kind === 'skill_activation' && entry.skillTrigger === 'user-slash') ||
entry.kind === 'plugin_command'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve plugin command args when undoing

When a plugin command with arguments is made undoable here, the /undo selector later restores formatUndoChoiceInput(entry), which only special-cases skill activations and otherwise falls back to entry.content. Plugin command transcript entries store content as just /${pluginId}:${commandName} and keep the typed args in pluginCommandData.args, so selecting an undone /plugin:command prod refills the editor as /plugin:command and silently drops prod. Add plugin-command formatting alongside the skill-activation path for both the selector label and restored input.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 9a90d8152e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@wbxl2000 wbxl2000 merged commit 5cb80ce into main Jun 30, 2026
9 checks passed
@wbxl2000 wbxl2000 deleted the feat/plugin-commands branch June 30, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant