Skip to content

feat(tui): add hierarchical skill group selector with tab navigation - #2995

Open
creatiVision wants to merge 5 commits into
MoonshotAI:mainfrom
creatiVision:feat/skill-group-selector
Open

feat(tui): add hierarchical skill group selector with tab navigation#2995
creatiVision wants to merge 5 commits into
MoonshotAI:mainfrom
creatiVision:feat/skill-group-selector

Conversation

@creatiVision

@creatiVision creatiVision commented Aug 17, 2026

Copy link
Copy Markdown

Problem

When managing multiple skills across categories or directory trees (e.g. cv/ops/semaphore, security/audit), the /skill command previously rendered skills in flat lists or cluttered pseudo-groups without clear visual boundaries. Furthermore:

  1. Navigating top-level categories was less intuitive compared to the /model selector's tab strip navigation.
  2. Typing /skill in the terminal input editor re-triggered argument autocomplete suggestions, requiring users to select /skill a second time before entering interactive selection mode.

What changed

  1. Tab Strip Group Navigation:

    • Integrated renderTabStrip utility into SkillSelectorComponent (apps/kimi-code/src/tui/components/dialogs/skill-selector.ts).
    • Top-level skill groups are now rendered in a tab strip ([All] [cv] [security] [Uncategorized]).
    • Pressing Tab / Shift-Tab cycles through top-level group tabs with brand-highlighted backgrounds, matching the exact UX of the /model selector (TabbedModelSelectorComponent).
  2. Hierarchical Group & Category Resolution:

    • Refactored buildSkillGroupTree and resolveGroupPathsForSkill in skill-group-tree.ts to strictly enforce metadata precedence order:
      1. groups (string array in skill metadata)
      2. category (slash-delimited string in skill metadata)
      3. Parent directory path relative to skill root
      4. Fallback to Uncategorized

    Examples of Metadata & Category Interaction

    • Example A: Explicit groups metadata (Highest Priority)

      name: semaphore-deploy
      groups: ["cv/ops", "deployments"]

      Result: The skill is placed under both cv › ops and deployments group tabs. Explicit groups override any category field or folder location.

    • Example B: category metadata

      name: static-audit
      category: security/static-analysis

      Result: Placed under security › static-analysis. Used when groups is not defined.

    • Example C: Relative Directory Path (Automatic Hierarchy)
      File location: ~/.gemini/skills/cv/ops/semaphore/SKILL.md
      Result: Automatically derived as cv › ops › semaphore without requiring metadata configuration.

    • Example D: Fallback
      A top-level skill with no groups, no category, and no subfolder location lands in the Uncategorized top-level tab.

  3. Subtree Fuzzy Search & Immediate Invocation:

    • Added filterItem callback to SearchableList so fuzzy searching queries match direct skills as well as descendant skills in subtrees when a search query is active, while keeping direct lists clean when query is empty.
    • Updated custom-editor.ts to suppress subcommand autocomplete reopening for /skill and /skills.
    • Updated handleSkillCommand in dispatch.ts to immediately launch runSkillSelector when running /skill without arguments.

Checklist

  • I have read the CONTRIBUTING document.
  • I have 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.

@changeset-bot

changeset-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3fb8bda

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

@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: 48b17c7e4a

ℹ️ 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 +337 to +339
if (page.items.length === 0 && query.workspaceIds !== undefined) {
const legacy = await this.listLegacy(query);
if (legacy.items.length > 0) return legacy;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Merge authoritative sessions when the cache is partially populated

When a workspace contains both a projected session and an older on-disk session missing from the read model, page.items.length is nonzero, so the authoritative fallback never runs and /sessions and --continue still hide the older session. The fallback must reconcile authoritative results with every workspace-scoped page, not only completely empty pages.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L81-L83

Useful? React with 👍 / 👎.

Comment on lines +210 to +214
name: 'skill',
aliases: ['skills'],
description: 'Select skill from hierarchical group selector',
priority: 90,
availability: 'always',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Block /skill while a turn is active

When the session is streaming or compacting, this always availability bypasses the busy gate that existing dynamic skill commands use. Selecting a skill then calls sendSkillActivation during the active turn, where it can fail with an agent-busy error after the command has already been consumed; make this command idle-only or perform the same busy check immediately before activation.

Useful? React with 👍 / 👎.

Comment thread scripts/kimi-fork-update.sh Outdated
Comment on lines +18 to +21
*laptop*|kimi-*)
# kimi-laptop (Default)
FORK_REPO="${KIMI_FORK_REPO:-/media/work-data/002_cv-projects/cv_ai_kimi-code-cli-fork}"
REPO_DIR="${KIMI_FORK_SYMLINK:-/home/mb/.kimi-code-cli}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove machine-specific identifiers from the public script

This committed script exposes a real user home path and workstation-specific repository layout, with additional private host and fork identifiers later in the file. Besides leaking internal context, hosts matching the broad patterns silently select these destructive defaults; require environment configuration and use neutral placeholders in the tracked script.

AGENTS.md reference: AGENTS.md:L78-L80

Useful? React with 👍 / 👎.

Comment thread .gitignore Outdated
Comment on lines +48 to +49
plugins/**
plugins

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 the official plugin ignore exceptions

The final plugins/** and plugins rules override the earlier !plugins/official/ and !plugins/marketplace.json exceptions. Consequently, newly generated or synced files under plugins/official are ignored and can be omitted from a release without appearing in git status; keep the parent traversable and re-include the intended paths after the broad ignore rule.

Useful? React with 👍 / 👎.

Comment on lines +101 to +107
const parts = pathStr.split('/');
let current: Json = root;
for (const part of parts) {
if (typeof current !== 'object' || current === null || Array.isArray(current)) {
throw new Error(`Unable to resolve reference path: ${pointer}`);
}
current = (current as JsonRecord)[part] ?? null;

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 Decode escaped JSON Pointer segments before dereferencing

Local JSON Schema references use JSON Pointer escaping, so a definition key containing / or ~ is referenced with ~1 or ~0. Splitting and looking up the raw segments makes valid schemas such as #/$defs/a~1b appear unresolved, causing listTools() to fail for that MCP server now that every schema is passed through this dereferencer; decode each segment before lookup.

Useful? React with 👍 / 👎.

@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from 48b17c7 to 5c0a79d Compare August 17, 2026 06:08
@creatiVision

Copy link
Copy Markdown
Author

Rebased branch onto latest upstream main — merge conflicts are resolved and PR history is cleaned down to the 3 relevant commits for the skill selector feature.

@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from 115be53 to 8c750f5 Compare August 17, 2026 23:23
@creatiVision

Copy link
Copy Markdown
Author

Addressed Codex feedback:

  • Busy-gated /skill command: Updated /skill in registry.ts to default idle availability and added active turn/compacting busy checks in handleSkillCommand before opening the selector dialog and before calling sendSkillActivation.
  • Note: The other 4 automated comments originated from earlier fork commits that have since been cleaned up during the rebase onto origin/main.

@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from 8c750f5 to 995bcc8 Compare August 17, 2026 23:28
@creatiVision

Copy link
Copy Markdown
Author

🔄 Status Update & Re-Review Request

The branch is cleanly rebased and synchronized with upstream main. All feedback from the automated review on commit 48b17c7e4a has been addressed:

  1. Busy-gated /skill command: Updated /skill in registry.ts to enforce idle availability and added active turn/compacting busy checks in handleSkillCommand before opening the selector dialog or sending skill activations (commit 995bcc8).
  2. Focused & Clean Scope: All unrelated commits (session index fallbacks, MCP schema sanitizer, helper scripts) have been removed from this branch and isolated in their dedicated PRs (#2952 and #830).
  3. Changeset: Minor changeset included for @moonshot-ai/kimi-code.
  4. Validation: Full test suite and linting pass cleanly.

@codex review

@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: da597a08fa

ℹ️ 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".

"@moonshot-ai/kimi-code": minor
---

Add hierarchical group navigation selector for the /skill command. Run /skill to open the interactive selector.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reduce the changeset to a single sentence

Rewrite this as one short user-facing sentence stating only the change; the second usage-instruction sentence violates the repository’s required changeset format and will flow into release notes.

AGENTS.md reference: AGENTS.md:L85-L87

Useful? React with 👍 / 👎.

Comment on lines +44 to +48
readonly category?: string | undefined;
readonly categories?: readonly string[] | undefined;
readonly issuer?: string | undefined;
readonly collection?: string | undefined;
readonly groups?: readonly string[] | undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove undefined unions from the new optional fields

Declare these newly added fields as category?: string, categories?: readonly string[], and so on; the same redundant unions were added to the metadata and mirrored v1/RPC declarations, contrary to the repository’s optional-property convention.

AGENTS.md reference: AGENTS.md:L55-L57

Useful? React with 👍 / 👎.

Comment on lines +648 to +650
let skills: readonly SkillSummary[] = [];
try {
skills = await session.listSkills();

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 Recheck the busy state after loading skills

Fresh evidence in this revision is the still-uncovered await session.listSkills() after the new initial gate: because slash dispatch is fire-and-forget, another prompt can start a turn while this call is pending. In that case /skill <name> reaches sendSkillActivation without another check, while bare /skill opens its dialog during the active turn; recheck immediately after this await before either branch.

Useful? React with 👍 / 👎.

}
}

const selectedSkill = await runSkillSelector(host, activatableSkills);

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 Pass configured skill roots into the selector

When a session uses the supported --skills-dir option with an arbitrary root such as /opt/company-capabilities, this production call omits the roots even though runSkillSelector accepts them. A nested skill such as team/deploy/SKILL.md then cannot be relativized, and because its path contains neither the hard-coded skills nor skillshub marker, it is incorrectly placed in Uncategorized instead of team.

Useful? React with 👍 / 👎.

Comment on lines +49 to +53
function countSkillsInTree(node: SkillGroupNode): number {
let count = node.skills.length;
for (const child of node.childGroups) {
count += countSkillsInTree(child);
}

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 each skill only once per group subtree

When metadata assigns a skill to both a parent and a child, as the added ['cv', 'cv/ops'] tests explicitly allow, this recursive sum counts that skill at every assigned level. The root UI can therefore report five skills for a subtree containing only two unique skills; calculate the count from unique skill identities or names across the subtree.

Useful? React with 👍 / 👎.

: '';

const hintParts = ['↑↓ navigate', 'Tab switch group'];
if (view.page.pageCount > 1) hintParts.push('←→ page');

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 Implement the advertised left and right paging keys

For lists spanning multiple pages, the dialog advertises ←→ page, but handleInput never handles Key.left or Key.right, and SearchableList.handleKey intentionally handles only PgUp/PgDn. Consequently both advertised arrow keys do nothing; dispatch them through matchesKey to pageUp() and pageDown(), or change the hint to the keys that actually work.

AGENTS.md reference: apps/kimi-code/AGENTS.md:L52-L52

Useful? React with 👍 / 👎.

@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from da597a0 to 818a052 Compare August 18, 2026 08:50
creatiVision pushed a commit to creatiVision/kimi-code that referenced this pull request Aug 18, 2026
- Recheck busy state after loading skills in /skill command
- Add idle-only availability to /skill command to block during active turns
- Implement left/right arrow key pagination in skill selector
- Count each skill only once per group subtree in skill selector
@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from 46f0be6 to 248c69b Compare August 18, 2026 14:07
mb added 5 commits August 20, 2026 22:00
- Recheck busy state after loading skills in /skill command
- Add idle-only availability to /skill command to block during active turns
- Implement left/right arrow key pagination in skill selector
- Count each skill only once per group subtree in skill selector
@creatiVision
creatiVision force-pushed the feat/skill-group-selector branch from 248c69b to 3fb8bda Compare August 20, 2026 22:00
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