fix(agents): Complete project skills and custom commands in the agent composer

The `/` completion of the New Task page only listed the commands the agent
offered in its last session, cached once for all projects, and matched them
by substring. Before a project's first session, or after a session in another
project, its skills were missing, and `/trig` never matched `/triage`.

- Remember the commands an agent offered per project root (settings stay shared).
- New `agent_commands` command: the agent's last list for the project (or the
  task's own list in a session) merged with, for Claude Code, the skills
  (`.claude/skills/*/SKILL.md`) and custom commands (`.claude/commands/**/*.md`,
  nested as `dir:name`) found from the working directory upward and in
  `~/.claude`. The agent's entries win on a name; skills with
  `user-invocable: false` are left out. Discovery runs off the command thread.
- Sessions discover from the task's own directory (worktree or checkout).
- Command matching ranks prefix, plugin-name prefix, substring, then letters in order.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
l0ng-ai committed 2026-10-27 20:59:52 +08:00
1 джерело 8568ff001b
коміт 34828bd710
13 files changed
+576 -32

No files matched your search

+15 -1
Переглянути файл
@@ -1,6 +1,6 @@
use crate::state::AppState;
use ide_agent::{
AgentChange, AgentEntry, AgentEvent, AgentPrompt, AgentProvider, AgentQueued, AgentTask, NewAgentTask,
AgentChange, AgentCommand, AgentEntry, AgentEvent, AgentPrompt, AgentProvider, AgentQueued, AgentTask, NewAgentTask,
};
use ide_core::{IdeResult, IpcPath};
use std::sync::Arc;
@@ -27,6 +27,20 @@ pub fn agent_providers(window: Window, state: State<'_, AppState>) -> IdeResult<
Ok(state.project(window.label())?.agents.providers())
}
/// Slash commands to complete for `provider` in `task`'s directory, or in the project
/// before a task exists (skills and custom commands on disk included).
#[tauri::command]
#[specta::specta]
pub async fn agent_commands(
window: Window,
state: State<'_, AppState>,
provider: String,
task: Option<String>,
) -> IdeResult<Vec<AgentCommand>> {
let project = state.project(window.label())?;
project.agents.commands(&provider, task.as_deref()).await
}
#[tauri::command]
#[specta::specta]
pub fn agent_entries(window: Window, state: State<'_, AppState>, task: String) -> IdeResult<Vec<AgentEntry>> {
+1
Переглянути файл
@@ -62,6 +62,7 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
app::log_frontend,
agent::agent_subscribe,
agent::agent_providers,
agent::agent_commands,
agent::agent_entries,
agent::agent_create,
agent::agent_prompt,
+1
Переглянути файл
@@ -417,6 +417,7 @@ impl AppState {
.join(&project_key),
models_file: self.data_dir.join("agents").join("models.json"),
custom_agents_dir: Some(self.config_dir.join("agents")),
home_dir: dirs::home_dir(),
},
tauri::async_runtime::handle().inner().clone(),
));