Skip to content

feat: slash commands and skills from local repo#3757

Open
lmtr0 wants to merge 1 commit into
pingdotgg:mainfrom
lmtr0:feat/local-repo-skills-slash-commands
Open

feat: slash commands and skills from local repo#3757
lmtr0 wants to merge 1 commit into
pingdotgg:mainfrom
lmtr0:feat/local-repo-skills-slash-commands

Conversation

@lmtr0

@lmtr0 lmtr0 commented Jul 7, 2026

Copy link
Copy Markdown

Why

I use skills to organize my projects, having the ability to access them easier inside t3 would be a game changer.

Why am I doing it? I saw the issue, thought it would be a quick fix; it actually is, and started using my fork; it improved my experience, so I thought it would be a good PR.

Closes #3576

What Changed

Adds project-scoped provider skills and slash-commands discovery.

Code Changes

  • Added a providers.projectCapabilities RPC with shared contract schemas.
  • Added provider registry support for project-scoped capability lookups.
  • Implemented project capability probes for Codex, Claude, Cursor, and OpenCode.
  • Updated web and mobile composers to use project/worktree-scoped capabilities for slash commands and skills. instead of the global alternative

Testing

  • vp run typecheck
  • vp check

UI Changes

For context, here is a test skill:
image

Current nightly app on a repo with multiple skills and commands, in opencode:
image

Same repo, nightly, in codex:
image

My pr, same repo, in opencode:
image
image

same repo, in codex:
image
image

Checklist

  • This PR is small and focused
  • explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes
    • No animations or interaction effects

Note

Medium Risk
New provider subprocess/SDK probes on composer open add latency and failure modes, but results are cached and errors fall back to empty capabilities without blocking sends.

Overview
Composer skills and slash commands now come from the active project/worktree cwd instead of the global provider snapshot on serverConfig.

A new providers.projectCapabilities RPC (contracts + WS handler) takes providerInstanceId, cwd, and optional forceReload. The provider registry exposes listProjectCapabilities, with per-driver composerCapabilities.list probes for Claude (init probe + skills), Codex (skills/list), Cursor (ACP available_commands_update), and OpenCode (command.list), mostly behind 30s per-cwd caches; failures degrade to empty lists.

Web and mobile add useProviderProjectCapabilities and use it in thread/new-task composers and feeds; thread detail passes threadCwd ?? projectWorkspaceRoot into capability lookups.

Reviewed by Cursor Bugbot for commit c7aa04b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add project-scoped slash commands and skills from local repo for all providers

  • Adds a new providers.projectCapabilities WebSocket RPC that returns slash commands and skills scoped to a specific project cwd and provider instance, defined in packages/contracts/src/rpc.ts and packages/contracts/src/server.ts
  • Each provider driver (Claude, Codex, Cursor, OpenCode) gains a composerCapabilities.list(cwd, forceReload?) method backed by a per-cwd cache with a shared TTL, replacing the global provider snapshot
  • Web and mobile composer components (ChatComposer, ThreadComposer) now call the new useProviderProjectCapabilities hook instead of reading from serverConfig, so displayed slash commands and skills reflect the active project directory
  • ProviderRegistry.listProjectCapabilities delegates to the driver's composerCapabilities.list and returns empty arrays for unsupported instances or on failure
  • Behavioral Change: slash commands and skills are now fetched lazily per (instanceId, cwd) pair; components will briefly show empty lists while the request is in flight
📊 Macroscope summarized c7aa04b. 22 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 12b208f3-5c87-4768-8efd-100f56af4091

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 7, 2026
@lmtr0

lmtr0 commented Jul 7, 2026

Copy link
Copy Markdown
Author

Tested codex, opencode, and claude

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

{composerTrigger && composerMenuItems.length > 0 ? (

When the user types a skill trigger (e.g. $) or a slash-command query matching only provider commands, the popover never appears and the user sees nothing instead of the Loading… or No skills found. feedback. composerMenuItems is empty until useProviderProjectCapabilities resolves, and the render guard at line 695 checks composerMenuItems.length > 0 — so ComposerCommandPopover is never mounted and its loading/empty states are unreachable. The guard should also allow rendering when the trigger is active and the async lookup is still pending.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/ThreadComposer.tsx around line 695:

When the user types a skill trigger (e.g. `$`) or a slash-command query matching only provider commands, the popover never appears and the user sees nothing instead of the `Loading…` or `No skills found.` feedback. `composerMenuItems` is empty until `useProviderProjectCapabilities` resolves, and the render guard at line 695 checks `composerMenuItems.length > 0` — so `ComposerCommandPopover` is never mounted and its loading/empty states are unreachable. The guard should also allow rendering when the trigger is active and the async lookup is still pending.

? { cwd: input.cwd }
: { cwd: input.cwd, forceReload: input.forceReload };
return yield* instance.composerCapabilities.list(request).pipe(
Effect.catchCause((cause) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium Layers/ProviderRegistry.ts:506

listProjectCapabilities catches every failure from instance.composerCapabilities.list() and returns a successful empty result with slashCommands: [] / skills: [], so transient errors (provider process crash, filesystem read failure, child-process error) are silently swallowed. Clients receive an empty-capabilities response and cache or display "no capabilities" instead of a failure they can retry. Consider only catching expected "unsupported" failures, or propagating transient errors to the caller so they can be surfaced and retried.

Also found in 1 other location(s)

apps/server/src/provider/Layers/CursorProvider.ts:485

probeCursorProjectSlashCommands swallows any acp.start() failure with Effect.ignore and then still waits on Deferred.await(commands) until the 3s timeout expires. When Cursor is unavailable (CLI missing, startup/auth failure, ACP handshake failure), project capability lookups block for CURSOR_ACP_COMMAND_DISCOVERY_TIMEOUT_MS before returning [] instead of resolving immediately as an unavailable provider. This adds a repeatable multi-second stall to composer capability loading on every cache miss or forced reload.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ProviderRegistry.ts around line 506:

`listProjectCapabilities` catches every failure from `instance.composerCapabilities.list()` and returns a successful empty result with `slashCommands: []` / `skills: []`, so transient errors (provider process crash, filesystem read failure, child-process error) are silently swallowed. Clients receive an empty-capabilities response and cache or display "no capabilities" instead of a failure they can retry. Consider only catching expected "unsupported" failures, or propagating transient errors to the caller so they can be surfaced and retried.

Also found in 1 other location(s):
- apps/server/src/provider/Layers/CursorProvider.ts:485 -- `probeCursorProjectSlashCommands` swallows any `acp.start()` failure with `Effect.ignore` and then still waits on `Deferred.await(commands)` until the 3s timeout expires. When Cursor is unavailable (CLI missing, startup/auth failure, ACP handshake failure), project capability lookups block for `CURSOR_ACP_COMMAND_DISCOVERY_TIMEOUT_MS` before returning `[]` instead of resolving immediately as an unavailable provider. This adds a repeatable multi-second stall to composer capability loading on every cache miss or forced reload.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

When useProviderProjectCapabilities resolves after the feed is already visible, already-rendered messages keep using the old empty skills array instead of the newly loaded one. ThreadFeed passes props.skills into renderItem, but omits it from the extraData object that forces LegendList to repaint visible rows — and LegendList does not invalidate rows when only the render callback changes. So skill highlighting stays stale on existing messages until some unrelated list invalidation occurs.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/ThreadDetailScreen.tsx around line 320:

When `useProviderProjectCapabilities` resolves after the feed is already visible, already-rendered messages keep using the old empty `skills` array instead of the newly loaded one. `ThreadFeed` passes `props.skills` into `renderItem`, but omits it from the `extraData` object that forces `LegendList` to repaint visible rows — and `LegendList` does not invalidate rows when only the render callback changes. So skill highlighting stays stale on existing messages until some unrelated list invalidation occurs.

};
}

export function useProviderProjectCapabilities(target: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High state/queries.ts:222

useProviderProjectCapabilities returns empty skills and slashCommands whenever providerInstanceId is null or undefined. Callers like ChatView fall back to defaultInstanceIdForDriver(selectedProvider) when no explicit instance ID is set, so the default provider silently loses skill suggestions and timeline skill chips because the hook never loads capabilities for the implicit instance. Consider resolving the implicit default instance before the null/undefined check, so the query runs for the default provider when providerInstanceId is not explicitly set.

Also found in 1 other location(s)

apps/mobile/src/state/queries.ts:128

useProviderProjectCapabilities unconditionally queries projectEnvironment.projectCapabilities whenever providerInstanceId and cwd are present, even if the selected provider instance has been disabled. On the server, disabled ProviderInstances are still kept in liveSubsRef and still expose composerCapabilities, so after a provider is turned off the composer can keep showing that provider's project skills and slashCommands. Trigger: open a thread/new task using one provider, disable that provider in settings, then reopen the composer; it still offers commands/skills from a provider that is no longer enabled.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/state/queries.ts around line 222:

`useProviderProjectCapabilities` returns empty `skills` and `slashCommands` whenever `providerInstanceId` is `null` or `undefined`. Callers like `ChatView` fall back to `defaultInstanceIdForDriver(selectedProvider)` when no explicit instance ID is set, so the default provider silently loses skill suggestions and timeline skill chips because the hook never loads capabilities for the implicit instance. Consider resolving the implicit default instance before the null/undefined check, so the query runs for the default provider when `providerInstanceId` is not explicitly set.

Also found in 1 other location(s):
- apps/mobile/src/state/queries.ts:128 -- `useProviderProjectCapabilities` unconditionally queries `projectEnvironment.projectCapabilities` whenever `providerInstanceId` and `cwd` are present, even if the selected provider instance has been disabled. On the server, disabled `ProviderInstance`s are still kept in `liveSubsRef` and still expose `composerCapabilities`, so after a provider is turned off the composer can keep showing that provider's project `skills` and `slashCommands`. Trigger: open a thread/new task using one provider, disable that provider in settings, then reopen the composer; it still offers commands/skills from a provider that is no longer enabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium threads/new-task-flow-provider.tsx:384

useProviderProjectCapabilities is called with cwd: selectedProject?.workspaceRoot || null, so skills are always loaded from the project's base checkout even when the draft selects a worktree path. Switching the new-task flow to worktree mode and choosing a branch in a different worktree leaves selectedProviderSkills reflecting the original checkout, so the composer surfaces the wrong repo-local skills for the task being created. Consider passing the draft's selectedWorktreePath (falling back to workspaceRoot) as the cwd argument so the capability query targets the same working directory the task will use.

  const selectedProviderCapabilities = useProviderProjectCapabilities({
    environmentId: selectedProject?.environmentId ?? null,
    providerInstanceId: selectedModel?.instanceId,
    cwd: selectedWorktreePath || selectedProject?.workspaceRoot || null,
  });
Also found in 1 other location(s)

apps/mobile/src/state/queries.ts:139

useProviderProjectCapabilities validates target.cwd with target.cwd.trim().length > 0 but then sends the original untrimmed target.cwd at input.cwd. The RPC schema uses TrimmedNonEmptyString, whose encode/decode path trims whitespace, so repositories whose actual path starts or ends with spaces will be queried under a different path. In that case project capabilities are looked up in the wrong directory or fail entirely.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/new-task-flow-provider.tsx around lines 384-3889276348489318:

`useProviderProjectCapabilities` is called with `cwd: selectedProject?.workspaceRoot || null`, so skills are always loaded from the project's base checkout even when the draft selects a worktree path. Switching the new-task flow to worktree mode and choosing a branch in a different worktree leaves `selectedProviderSkills` reflecting the original checkout, so the composer surfaces the wrong repo-local skills for the task being created. Consider passing the draft's `selectedWorktreePath` (falling back to `workspaceRoot`) as the `cwd` argument so the capability query targets the same working directory the task will use.

Also found in 1 other location(s):
- apps/mobile/src/state/queries.ts:139 -- `useProviderProjectCapabilities` validates `target.cwd` with `target.cwd.trim().length > 0` but then sends the original untrimmed `target.cwd` at `input.cwd`. The RPC schema uses `TrimmedNonEmptyString`, whose encode/decode path trims whitespace, so repositories whose actual path starts or ends with spaces will be queried under a different path. In that case project capabilities are looked up in the wrong directory or fail entirely.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c7aa04b. Configure here.

environmentId,
providerInstanceId: activeProviderInstanceId,
cwd: gitCwd,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Timeline skills miss provider fallback

Medium Severity

The message timeline's skill highlighting is missing because useProviderProjectCapabilities doesn't fetch skills when activeProviderInstanceId is null. This can occur even when a default provider is active, resulting in an empty skills list for the timeline.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c7aa04b. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

5 blocking correctness issues found. This PR introduces a new feature (project-scoped slash commands and skills) with new RPC endpoints, server-side capability probing across multiple providers, and client-side hooks. Multiple unresolved review comments identify substantive bugs including silent failures when using default provider instances. Warrants human review for both the feature scope and open issues.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex repo-local .agents/skills are not discovered in project threads

1 participant