mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
feat(agents): new tasks default to the bypass permission mode (#444)
New agent tasks start in the agent's mode that approves everything without asking (bypassPermissions, yolo, full-access…) or with approvals bypassed, unless the New Task page sets something else.
This commit is contained in:
1 parent
195356c4b1
commit
bc70ec7028
5 files changed
+33
-7
No files matched your search
@@ -37,6 +37,16 @@ pub(crate) fn bypass_approvals(provider: &str, config: &[AgentConfigOption]) ->
|
||||
provider == "cursor" && config.iter().any(|o| o.id == CURSOR_APPROVAL && o.current.as_deref() == Some("bypass"))
|
||||
}
|
||||
|
||||
/// The mode that approves everything without asking (`bypassPermissions`, `yolo`, `full-access`…), if the agent has one.
|
||||
pub(crate) fn bypass_mode(options: &[AgentConfigOption]) -> Option<(&AgentConfigOption, &AgentConfigValue)> {
|
||||
let option = options.iter().find(|o| o.category.as_deref() == Some("mode"))?;
|
||||
let value = option.values.iter().find(|v| {
|
||||
let m: String = v.value.to_lowercase().chars().filter(char::is_ascii_lowercase).collect();
|
||||
m.contains("bypass") || m == "yolo" || m.contains("fullaccess") || m == "dangerous"
|
||||
})?;
|
||||
Some((option, value))
|
||||
}
|
||||
|
||||
/// The settings in a `session/new` or `session/load` result: its select `configOptions`,
|
||||
/// plus the older `modes` / `models` fields for what those don't cover.
|
||||
pub(crate) fn options_of(result: &Value) -> Vec<AgentConfigOption> {
|
||||
|
||||
@@ -117,9 +117,13 @@ impl Shared {
|
||||
fn known_config(&self, provider: &str) -> Vec<AgentConfigOption> {
|
||||
let mut config = self.known.lock().get(provider).map(|k| k.config.clone()).unwrap_or_default();
|
||||
config::add_client_options(provider, &mut config);
|
||||
// Approval bypass belongs to the conversation, never to another new task.
|
||||
if let Some((o, v)) = config::bypass_mode(&config).map(|(o, v)| (o.id.clone(), v.value.clone()))
|
||||
&& let Some(o) = config.iter_mut().find(|c| c.id == o)
|
||||
{
|
||||
o.current = Some(v);
|
||||
}
|
||||
if let Some(o) = config.iter_mut().find(|o| o.id == config::CURSOR_APPROVAL) {
|
||||
o.current = Some("ask".into());
|
||||
o.current = Some("bypass".into());
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
@@ -793,13 +793,13 @@ async fn cursor_approval_switch_resolves_pending_and_survives_restart() {
|
||||
mgr.prompt(&task.id, text("Fix again"), false).await.unwrap();
|
||||
wait_for(&mgr, &task.id, AgentStatus::Ready).await;
|
||||
assert!(!agent.calls.lock().iter().any(|c| c.contains("bypass")));
|
||||
assert!(!config::bypass_approvals("cursor", &mgr.shared.known_config("cursor")));
|
||||
assert!(config::bypass_approvals("cursor", &mgr.shared.known_config("cursor")));
|
||||
mgr.set_config(&task.id, config::CURSOR_APPROVAL, "ask".into()).await.unwrap();
|
||||
mgr.prompt(&task.id, text("Fix once more"), false).await.unwrap();
|
||||
wait_for(&mgr, &task.id, AgentStatus::NeedsInput).await;
|
||||
mgr.cancel(&task.id, None).unwrap();
|
||||
wait_for(&mgr, &task.id, AgentStatus::Ready).await;
|
||||
// An unrelated new conversation still asks, even after the cache was updated.
|
||||
// A task created without choosing approvals still asks; the New Task page sends the default.
|
||||
let other = mgr.create(new()).await.unwrap();
|
||||
wait_for(&mgr, &other.id, AgentStatus::NeedsInput).await;
|
||||
mgr.cancel(&other.id, None).unwrap();
|
||||
|
||||
@@ -149,6 +149,17 @@ export function modeIcon(mode: string): string {
|
||||
return "lock";
|
||||
}
|
||||
|
||||
/** The settings that approve everything without asking (a bypass mode, or approvals), by option id. */
|
||||
export function bypassDefaults(config: readonly AgentConfigOption[]): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
const mode = modeOption(config);
|
||||
const unlock = mode?.values.find((v) => modeIcon(v.value) === "unlock");
|
||||
if (mode && unlock) out[mode.id] = unlock.value;
|
||||
const approval = config.find((o) => o.category === "approval");
|
||||
if (approval?.values.some((v) => v.value === "bypass")) out[approval.id] = "bypass";
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Shift+Tab: the mode after the current one. */
|
||||
export function nextValue(option: AgentConfigOption, current = option.current): string | null {
|
||||
if (!option.values.length) return null;
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
} from "@/bindings";
|
||||
import { call, commands, isTauri } from "@/platform/ipc";
|
||||
import { editorService, notifications } from "@/workbench/contributions";
|
||||
import { canSend, emptyDraft, toPrompt, type ComposerDraft, type FileRef } from "./composerModel";
|
||||
import { bypassDefaults, canSend, emptyDraft, toPrompt, type ComposerDraft, type FileRef } from "./composerModel";
|
||||
import { withQuote } from "./conversation";
|
||||
import {
|
||||
HOME_KEY,
|
||||
@@ -348,14 +348,15 @@ export function openHome(prompt?: string) {
|
||||
|
||||
/** `isolation` overrides the draft's (a project without git can only use the local checkout). */
|
||||
export async function startTask(isolation: Isolation = useAgents.getState().draft.isolation): Promise<void> {
|
||||
const { draft } = useAgents.getState();
|
||||
const { draft, providers } = useAgents.getState();
|
||||
const composer = composerDraft(HOME);
|
||||
if (!canSend(composer)) return;
|
||||
const config = { ...bypassDefaults(providers.find((p) => p.id === draft.provider)?.config ?? []), ...draft.config };
|
||||
const task = await call(
|
||||
commands.agentCreate({
|
||||
prompt: toPrompt(composer),
|
||||
provider: draft.provider,
|
||||
config: Object.entries(draft.config).map(([id, value]) => ({ id, value })),
|
||||
config: Object.entries(config).map(([id, value]) => ({ id, value })),
|
||||
isolation,
|
||||
base: isolation === "worktree" ? draft.base : null,
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user