unified slash menu skill invocation - kkamegawa/vsextensionforcodex GitHub Wiki
Date: 2026-08-11
Tracking: Issue #140
Baseline: main at 249b2c4
Implementation: codex/140-unified-slash-menu at 6aee497
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.
- A leading
/at composer index 0 opens one inline, non-popup candidate list.//, mid-text slash,/skill,/skills, and$skillare 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
PendingSkillchip, clears only the/...query throughSetComposerText(""), 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
WaitingForApprovalturns 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/startwith an empty composer. The chip is cleared only after a matching successfulturn/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.
- 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 fulldefaultPromptare never Remote UI data members. - On every
turn/start, the Worker force-reloadsskills/listimmediately before validation. The selectedName + Scope + Pathmust exactly match an enabled current catalog entry. The app-server receives only{ "type": "skill", "name": "...", "path": "..." };Scopeandskill_approvalare never serialized. - Unknown skill-specific approval requests fail closed and do not flow through an existing grant.
- A
skills/changednotification, connection, restart, or disconnect advances the generation. An older completion can never replace a newer snapshot.
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 normalizedBrandColor.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 andTimeProvider. Only the newest live generation is published. Liveskills/listremains 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.
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.
-
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. - Unified presentation: one virtualized ListBox, built-in cap 8, all Worker-accepted skills up to 200, deterministic ranking/collisions, and distinct state rows.
- Composer lifecycle: independent one-slot chip, replacement/removal, query-only clearing, busy steer gate, default-prompt explicit action, and accessibility/live-region behavior.
- Metadata and security: independent field fallback, SafeMarkdown/ISecretRedactor boundaries, no raw paths or dependency execution, and fixed-glyph icon gate.
- 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.
- 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.
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.