run an agent from the file browser

Right-click an entry and agents whose triggers match appear under their own Run Agent
submenu — deliberately not folded in with tasks, because an agent run is a chat session and
not a job, and one menu promising both would lie about what a click does. The modal shows the
absolute target path, autofills entry_path with it (tilde expansion stays the server's job),
and on Run links to /chat?cwd=<runs dir> instead of a queue entry: there is no job row to view.

Trigger matching and category grouping are now shared with tasks rather than duplicated, and
the task input form is reused as-is.

Rescan counts agents and invalidates their caches, so a new AGENT.md shows up on the button
rather than after the 60s staleTime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 00:28:51 +00:00
co-authored by Claude Opus 5
parent 6b46d9f1f9
commit cc95f7fa17
11 changed files with 403 additions and 26 deletions
@@ -8,7 +8,7 @@ type RescanResponse = { ok: boolean; counts: Record<string, number> };
// Item query caches the header refreshes after a rescan (matches the queryKeys used by the
// Automation and Capability pages).
const ITEM_QUERY_KEYS = ['tasks', 'skills', 'tools', 'processes'];
const ITEM_QUERY_KEYS = ['tasks', 'task-categories', 'skills', 'tools', 'processes', 'agents', 'agent-categories'];
export function RescanButton() {
const client = useClient();
@@ -25,7 +25,9 @@ export function RescanButton() {
// you next open that page. This refreshes every item cache immediately, wherever you press it.
await Promise.all(ITEM_QUERY_KEYS.map((key) => qc.invalidateQueries({ queryKey: [key], refetchType: 'all' })));
const c = res.counts ?? {};
toast.success(`Rescanned items — ${c.tasks ?? 0} tasks, ${c.tools ?? 0} tools, ${c.skills ?? 0} skills, ${c.processes ?? 0} processes`);
toast.success(
`Rescanned items — ${c.tasks ?? 0} tasks, ${c.agents ?? 0} agents, ${c.tools ?? 0} tools, ${c.skills ?? 0} skills, ${c.processes ?? 0} processes`,
);
} catch {
toast.error('Failed to rescan items');
} finally {