unified slash menu skill invocation - kkamegawa/vsextensionforcodex GitHub Wiki

Unify Built-in Commands and Skill Invocation in the Slash Menu

日本語

Date: 2026-08-11
Tracking: Issue #140
Baseline: main at 249b2c4
Implementation: codex/140-unified-slash-menu at 6aee497

Background

The target experience is one inline slash menu, like the ChatGPT app, where built-in commands and locally available Codex skills are presented together. The current Worker already discovers skills through skills/list; this plan defines the complete Extension presentation, structured turn handoff, and the durable local catalog cache.

ADR-009 remains the record of the original v15 invocation design. ADR-010 supersedes only its Extension-side twenty-skill presentation cap and volatile-cache-only assumption. The Worker safety bound, flattened catalog, complete identity validation, missing-data tolerance, and fixed-glyph icon fallback remain in force.

Product decisions

  • A leading / at composer index 0 opens one inline, non-popup candidate list. //, mid-text slash, /skill, /skills, and $skill are out of scope.
  • Built-ins appear first and remain capped at 8. Skills follow in the same virtualized ListBox; every distinct (Name, Scope, Path) identity safely accepted by the Worker is presented, up to the Worker safety bound of 200.
  • Selecting a skill creates one independent removable PendingSkill chip, clears only the /... query through SetComposerText(""), and keeps the normal composer visible. Selecting another skill replaces the chip and announces the replacement through the polite live region.
  • Disabled, stale, and passive status rows remain visible but are not keyboard-selectable. A cached catalog is explicitly labeled as cached and is non-selectable until a live generation arrives.
  • Busy and WaitingForApproval turns allow selection, replacement, and removal. While a skill is pending, Send/steer is disabled with guidance to wait or remove the chip. Removing the chip restores the existing text-only steer behavior.
  • Ready state permits a skill-only turn/start with an empty composer. The chip is cleared only after a matching successful turn/start; refresh failure, stale validation, replacement during send, steer, review, and compact retain it.
  • # file suggestions remain exclusive with slash candidates. Keyboard navigation and accessibility keep the existing Up/Down/Home/End/PageUp/PageDown, Enter/Tab, Escape, and Ctrl+Enter roles.

Contract and data flow

  • Worker contract v15 contains nullable StartTurnRequest.Skill, SkillInvocationInfo { Name, Scope, Path }, validated metadata, SkillsChanged { Generation }, and the bridge list operation. Icon DTO/RPC fields remain omitted unless the Remote UI icon spike succeeds.
  • The Extension exposes only an opaque selection ID, a sanitized display name, and a fixed scope label. Raw Path, URLs, commands, dependency transport values, and full defaultPrompt are never Remote UI data members.
  • On every turn/start, the Worker force-reloads skills/list immediately before validation. The selected Name + Scope + Path must exactly match an enabled current catalog entry. The app-server receives only { "type": "skill", "name": "...", "path": "..." }; Scope and skill_approval are never serialized.
  • Unknown skill-specific approval requests fail closed and do not flow through an existing grant.
  • A skills/changed notification, connection, restart, or disconnect advances the generation. An older completion can never replace a newer snapshot.

Persistent catalog cache (ADR-010)

The Worker keeps the existing 60-second in-memory authoritative snapshot and adds a versioned persistent stale-while-revalidate snapshot under %LOCALAPPDATA%\\Kkamegawa.CodexForVisualStudio\\skill-catalog\\v1.

  • Cache keys are SHA-256 hashes of the normalized workspace path. Each record stores the format version, Codex version, saved UTC time, generation-independent truncation state, and at most 200 validated display/identity records.
  • A record has a 24-hour hard expiry, a 4 MiB per-workspace limit, and a 64 MiB total limit. Atomic temp-file replacement, LRU eviction, a bounded named cross-process mutex, and fail-open handling for missing, corrupt, oversized, or locked files are required.
  • Persisted fields are limited to Name, Description, ShortDescription, DisplayName, Scope, Path, Enabled, and normalized BrandColor. DefaultPrompt, dependency values, errors, raw app-server JSON, icon paths, and Remote UI IDs are not persisted.
  • A valid disk record is returned immediately as IsStale=true, marked cached/non-selectable in the Extension, and refreshed in the background with single-flight and TimeProvider. Only the newest live generation is published. Live skills/list remains authoritative for selection and turn start.
  • The Extension retains a generation-tagged display snapshot and requests the Worker only when the menu opens, the TTL expires, or a notification arrives; key presses never issue RPCs.

Search and presentation

The single virtualized list contains built-in rows, a Skills header, skill rows, and passive loading/empty/unsupported/failed/cached/truncated status rows. Built-in matching keeps the existing first-token rank and cap of 8. Skill matching uses the complete remainder after /, preserving spaces and dots.

Skill ranking is: Name exact, DisplayName exact, Name prefix, DisplayName prefix, word-boundary match, Name/DisplayName substring, then description substring. Stable tie-breakers are enabled state, repo/user/system/admin/unknown scope order, display name, Name, and Path. More than 200 Worker entries produces a distinct Worker-truncated row; any future presentation limit would be reported separately and is not used in the current implementation. Same-name/same-scope/different-path collisions use safe Repository 1 of 2-style suffixes without exposing paths.

Metadata remains display-only and field-validated: BrandColor is strict uppercase #RRGGBB and is a small accent only (hidden in High Contrast); DefaultPrompt is redacted, control-free, capped at 4096 characters, previewed safely, and inserted only by an explicit empty-composer action; dependencies are non-link plain-text badges/tooltips (maximum 16 entries, bounded type/value/description) with no execution, installation, readiness check, or approval.

The icon spike is still pending. Until an Extension-owned cache URI can be proven to bind and update in Remote UI, fixed glyphs are used and icon RPC/DTO fields are not enabled. If accepted later, Worker containment/PNG validation and Extension decode, metadata stripping, SHA-256 naming, atomic publication, 32-file/8 MiB LRU, and cross-instance locking are mandatory; Remote UI receives only the Extension cache URI.

Implementation phases

  1. Contract and Worker: v15 DTOs, exact serializer, authoritative memory cache, persistent cache store, invalidation/generation guard, sticky -32601, force reload, and fail-closed identity validation.
  2. Unified presentation: one virtualized ListBox, built-in cap 8, all Worker-accepted skills up to 200, deterministic ranking/collisions, and distinct state rows.
  3. Composer lifecycle: independent one-slot chip, replacement/removal, query-only clearing, busy steer gate, default-prompt explicit action, and accessibility/live-region behavior.
  4. Metadata and security: independent field fallback, SafeMarkdown/ISecretRedactor boundaries, no raw paths or dependency execution, and fixed-glyph icon gate.
  5. Verification and documentation: Core/UI/cache/serialization/security tests, warnings-as-errors Debug/Release builds, artifact and embedded-XAML hashes, and synchronized repository documents plus this English/Japanese Wiki plan.

Acceptance and verification

  • Cover 0, 1, 20, 21, 200, and 201 server entries; every accepted identity is reachable through keyboard navigation and UI Automation, while Worker truncation remains explicit.
  • Cover stale-to-live replacement, TTL boundaries, force reload, single-flight, notification races, workspace isolation, cache corruption/expiry/oversize, atomic concurrent writes, LRU cleanup, and fail-open live fallback.
  • Cover disabled/stale identity rejection, exact three-field turn serialization, absent scope, absent skill_approval, chip replacement/removal/success-only clear, busy steer gating, # exclusion, default-prompt non-send behavior, metadata redaction/fallback, and raw-data-member regressions.
  • Verify Debug and Release builds, full Core/UI tests, VSIX/DLL/XAML hash equality, and Dark/Light/Blue/High Contrast layout at narrow width and 200% DPI. Experimental Instance screenshots and the icon spike remain manual follow-up gates.

Non-goals

No separate skills panel or mode toggle, no new NuGet package, no mid-text slash trigger, no iconLarge/SVG, no arbitrary color background, no automatic prompt send, no dependency execution/installation/readiness/auto-approval, and no skill_approval turn field.

⚠️ **GitHub.com Fallback** ⚠️