Skip to content

Add per-tool and per-project YOLO mode#28

Merged
sidharthmirch merged 7 commits into
mainfrom
sidharthmirch/per-tool-yolo
Apr 9, 2026
Merged

Add per-tool and per-project YOLO mode#28
sidharthmirch merged 7 commits into
mainfrom
sidharthmirch/per-tool-yolo

Conversation

@sidharthmirch
Copy link
Copy Markdown
Owner

@sidharthmirch sidharthmirch commented Apr 8, 2026

Closes #(relevant issue if exists)

Adds finer-grained YOLO control beyond the existing all-or-nothing global toggle.

Changes

  • Migration 145: New tool_yolo table (per-tool auto-execute list) + projects.yolo_mode column (NULL = inherit, 0 = force off, 1 = force on)
  • ToolYoloAPI.ts (new): CRUD + React Query hooks for tool_yolo entries
  • ProjectAPI.ts: yoloMode field on Project type; fetchProjectYoloMode() (non-hook for ToolsetsManager); useSetProjectYoloMode() mutation
  • ToolsetsManager.ts: resolveYoloMode() precedence chain: per-project override → per-tool YOLO → global YOLO; projectId? param added to executeToolCall()
  • MessageAPI.ts: threads projectId through to executeToolCall()
  • PermissionsTab.tsx: new "Auto-accept specific tools" section (shown when global YOLO is off) with a switch per running tool
  • ProjectView.tsx: YOLO Mode selector (Inherit Global / Enabled / Disabled) after Default Prompt Profile

Test plan

  • App launches without DB errors (migration 145 runs cleanly)
  • Global YOLO toggle still works — all tools auto-execute when on
  • Per-tool YOLO: toggle a specific tool in Settings → Permissions; verify only that tool auto-executes when global YOLO is off, others still prompt
  • Per-project YOLO "Disabled": set on a project, confirm tools prompt even with global YOLO on
  • Per-project YOLO "Enabled": set on a project, confirm tools auto-execute even with global YOLO off
  • Per-project YOLO "Inherit Global": confirm normal global + per-tool behavior is restored
  • Existing always_allow/always_deny/ask preferences still work when all YOLO modes are off
  • Per-tool YOLO section is hidden when global YOLO is on (shows muted note instead)

- Migration 145: new `tool_yolo` table + `projects.yolo_mode` column
- ToolYoloAPI: CRUD hooks for per-tool YOLO entries
- ProjectAPI: yoloMode field, fetchProjectYoloMode, useSetProjectYoloMode
- ToolsetsManager: resolveYoloMode() precedence chain (per-project → per-tool → global)
- MessageAPI: thread projectId through to executeToolCall
- PermissionsTab: per-tool YOLO toggles section (shown when global YOLO is off)
- ProjectView: YOLO Mode selector (Inherit Global / Enabled / Disabled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 01:59
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds finer-grained YOLO (auto-execute) controls by introducing per-project overrides and per-tool allowlisting, and threads project context through tool execution so the correct precedence can be applied.

Changes:

  • Add tool_yolo table and projects.yolo_mode column to persist per-tool and per-project YOLO settings.
  • Introduce ToolYolo API helpers/hooks and expose per-project YOLO mode fetch/mutation in ProjectAPI.
  • Update tool execution flow to resolve YOLO mode via precedence (project → tool → global) and add UI controls for per-project/per-tool settings.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src-tauri/src/migrations.rs Adds migration for tool_yolo table and projects.yolo_mode column
src/core/chorus/api/ToolYoloAPI.ts New API module for CRUD/checking per-tool YOLO entries + React Query hooks
src/core/chorus/api/ProjectAPI.ts Adds yoloMode to Project type, DB mapping, fetch helper, and mutation
src/core/chorus/ToolsetsManager.ts Adds YOLO resolution precedence logic and threads projectId into tool execution
src/core/chorus/api/MessageAPI.ts Passes projectId through to tool execution during streaming
src/ui/components/PermissionsTab.tsx Adds per-tool auto-accept UI when global YOLO is off
src/ui/components/ProjectView.tsx Adds per-project YOLO mode selector (inherit/enabled/disabled)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src-tauri/src/migrations.rs Outdated
Comment on lines +2650 to +2664
Migration {
version: 145,
description: "add tool_yolo table and projects.yolo_mode column",
kind: MigrationKind::Up,
sql: r#"
CREATE TABLE IF NOT EXISTS tool_yolo (
toolset_name TEXT NOT NULL,
tool_name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (toolset_name, tool_name)
);

ALTER TABLE projects ADD COLUMN yolo_mode INTEGER DEFAULT NULL;
"#,
},
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Migration list is out of version order (144, 145, then 143). tauri_plugin_sql migrations are typically applied in the order provided, and out-of-order versions can cause startup migration failures or skipped migrations. Reorder these migration blocks so versions are strictly increasing (…142, 143, 144, 145) and keep new migrations appended in the correct position.

Copilot uses AI. Check for mistakes.
Comment thread src/core/chorus/ToolsetsManager.ts Outdated
Comment on lines +76 to +80
// 2. Per-tool YOLO
const isToolYolo = await checkToolYolo(toolsetName, toolName);
if (isToolYolo) {
return true;
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

resolveYoloMode always queries tool_yolo before checking global YOLO. Since the effective result is (projectOverride ?? (globalYolo || perToolYolo)), you can fetch global YOLO before checking per-tool and skip the tool_yolo query entirely when global YOLO is enabled. This avoids an extra DB roundtrip on every tool call.

Copilot uses AI. Check for mistakes.
Comment thread src/ui/components/PermissionsTab.tsx Outdated
Comment on lines +150 to +155
{!yoloMode && allTools.length > 0 && (
<div className="space-y-2">
<div className="space-y-1">
<h3 className="text-base font-semibold">
Auto-accept specific tools
</h3>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

useYoloMode() can return undefined while loading / when metadata is absent, but the UI treats falsy as "global YOLO off" (!yoloMode). This can briefly show the per-tool auto-accept section (or hide the note) even if global YOLO is actually enabled. Consider handling the loading/undefined state explicitly (e.g., yoloMode === false / yoloMode === true, or gate on the underlying app-metadata query readiness).

Copilot uses AI. Check for mistakes.
Comment thread src/ui/components/PermissionsTab.tsx Outdated
Comment on lines +177 to +181
<Label className="font-mono text-sm cursor-pointer">
{toolsetName}_{toolName}
</Label>
</div>
<Switch
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The per-tool Label isn’t associated with its Switch (no htmlFor/id), so clicking the label won’t toggle the control and screen readers won’t get a clear relationship. Add a stable id per tool switch and set Label htmlFor (or add an aria-label to the Switch).

Copilot uses AI. Check for mistakes.
Comment thread src/ui/components/PermissionsTab.tsx Outdated
Comment on lines +33 to +35
const { data: toolYoloEntries } = ToolYoloAPI.useAllToolYolo();
const setToolYolo = ToolYoloAPI.useSetToolYolo();
const deleteToolYolo = ToolYoloAPI.useDeleteToolYolo();
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

useAllToolYolo() runs unconditionally, even when global YOLO is enabled and the per-tool section is hidden. Consider setting the query’s enabled option based on yoloMode === false (and possibly allTools.length > 0) to avoid an unnecessary DB query.

Copilot uses AI. Check for mistakes.
Comment thread src/ui/components/PermissionsTab.tsx Outdated
Comment on lines +37 to +46
const allTools = React.useMemo(() => {
return ToolsetsManager.instance
.listToolsets()
.flatMap((toolset) =>
toolset.listTools().map((tool) => ({
toolsetName: tool.toolsetName,
toolName: tool.displayNameSuffix,
})),
);
}, []);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

allTools is memoized with an empty dependency array, so the list will never update if toolsets are started/stopped or refreshed while this tab is mounted. If tool availability can change at runtime, consider deriving this list from reactive state (or recomputing when toolset status/config changes).

Suggested change
const allTools = React.useMemo(() => {
return ToolsetsManager.instance
.listToolsets()
.flatMap((toolset) =>
toolset.listTools().map((tool) => ({
toolsetName: tool.toolsetName,
toolName: tool.displayNameSuffix,
})),
);
}, []);
const allTools = ToolsetsManager.instance
.listToolsets()
.flatMap((toolset) =>
toolset.listTools().map((tool) => ({
toolsetName: tool.toolsetName,
toolName: tool.displayNameSuffix,
})),
);

Copilot uses AI. Check for mistakes.
Comment thread src/core/chorus/api/ToolYoloAPI.ts Outdated
Comment on lines +40 to +44
const rows = await db.select<ToolYoloDBRow[]>(
"SELECT 1 FROM tool_yolo WHERE toolset_name = ? AND tool_name = ?",
[toolsetName, toolName],
);
return rows.length > 0;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

checkToolYolo runs SELECT 1 ... but types the result as ToolYoloDBRow[]. This is misleading and makes it easy to accidentally rely on fields that won’t exist. Either select the actual columns (or SELECT 1 AS exists) and update the row type to match.

Copilot uses AI. Check for mistakes.
- Fix migration ordering (142→143→144→145 instead of 142→144→145→143)
- Check global YOLO before per-tool to skip unnecessary DB query
- Fix checkToolYolo SELECT 1 typed as ToolYoloDBRow (now { exists })
- Add htmlFor/id to per-tool Label/Switch for accessibility
- Make useAllToolYolo conditional (skip query when global YOLO is on)
- Use yoloMode === false instead of !yoloMode to handle loading state
- Remove stale useMemo with empty deps for allTools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sidharthmirch
Copy link
Copy Markdown
Owner Author

WARNING CHECK BEFORE MERGING #29 #30 #31 DUE TO CONFLICTING MIGRATION #'s

@sidharthmirch sidharthmirch added the enhancement New feature or request label Apr 8, 2026
@sidharthmirch sidharthmirch merged commit 4a1c2c4 into main Apr 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants