feat: slash commands and skills from local repo#3757
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Tested codex, opencode, and claude |
There was a problem hiding this comment.
🟡 Medium
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) => |
There was a problem hiding this comment.
🟡 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
probeCursorProjectSlashCommandsswallows anyacp.start()failure withEffect.ignoreand then still waits onDeferred.await(commands)until the 3s timeout expires. When Cursor is unavailable (CLI missing, startup/auth failure, ACP handshake failure), project capability lookups block forCURSOR_ACP_COMMAND_DISCOVERY_TIMEOUT_MSbefore 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.
There was a problem hiding this comment.
🟡 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: { |
There was a problem hiding this comment.
🟠 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
useProviderProjectCapabilitiesunconditionally queriesprojectEnvironment.projectCapabilitieswheneverproviderInstanceIdandcwdare present, even if the selected provider instance has been disabled. On the server, disabledProviderInstances are still kept inliveSubsRefand still exposecomposerCapabilities, so after a provider is turned off the composer can keep showing that provider's projectskillsandslashCommands. 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.
There was a problem hiding this comment.
🟡 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
useProviderProjectCapabilitiesvalidatestarget.cwdwithtarget.cwd.trim().length > 0but then sends the original untrimmedtarget.cwdatinput.cwd. The RPC schema usesTrimmedNonEmptyString, 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.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ 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, | ||
| }); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit c7aa04b. Configure here.
ApprovabilityVerdict: 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. |


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
providers.projectCapabilitiesRPC with shared contract schemas.Testing
vp run typecheckvp checkUI Changes
For context, here is a test skill:

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

Same repo, nightly, in codex:

My pr, same repo, in opencode:


same repo, in codex:


Checklist
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.projectCapabilitiesRPC (contracts + WS handler) takesproviderInstanceId,cwd, and optionalforceReload. The provider registry exposeslistProjectCapabilities, with per-drivercomposerCapabilities.listprobes for Claude (init probe + skills), Codex (skills/list), Cursor (ACPavailable_commands_update), and OpenCode (command.list), mostly behind 30s per-cwd caches; failures degrade to empty lists.Web and mobile add
useProviderProjectCapabilitiesand use it in thread/new-task composers and feeds; thread detail passesthreadCwd ?? projectWorkspaceRootinto 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
providers.projectCapabilitiesWebSocket RPC that returns slash commands and skills scoped to a specific projectcwdand provider instance, defined in packages/contracts/src/rpc.ts and packages/contracts/src/server.tscomposerCapabilities.list(cwd, forceReload?)method backed by a per-cwdcache with a shared TTL, replacing the global provider snapshotChatComposer,ThreadComposer) now call the newuseProviderProjectCapabilitieshook instead of reading fromserverConfig, so displayed slash commands and skills reflect the active project directoryProviderRegistry.listProjectCapabilitiesdelegates to the driver'scomposerCapabilities.listand returns empty arrays for unsupported instances or on failure(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.