From de222e65ff911998f726a2bb54a2a4e38a2c776b Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 20 Jan 2026 07:37:23 +0100 Subject: [PATCH 1/2] Add agent actions to file menu --- src-tauri/src/lib.rs | 47 ++++++++++- src/App.tsx | 171 ++++++++++++++++++++++++++++++++--------- src/services/events.ts | 40 ++++++++++ 3 files changed, 222 insertions(+), 36 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c541c2f3f..e586dfe00 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -63,6 +63,19 @@ pub fn run() { ], )?; + let new_agent_item = + MenuItemBuilder::with_id("file_new_agent", "New Agent").build(handle)?; + let new_worktree_agent_item = + MenuItemBuilder::with_id("file_new_worktree_agent", "New Worktree Agent") + .build(handle)?; + let new_clone_agent_item = + MenuItemBuilder::with_id("file_new_clone_agent", "New Clone Agent") + .build(handle)?; + let add_workspace_item = + MenuItemBuilder::with_id("file_add_workspace", "Add Workspace...").build(handle)?; + let settings_item = + MenuItemBuilder::with_id("file_open_settings", "Settings...").build(handle)?; + #[cfg(target_os = "linux")] let file_menu = { let close_window_item = @@ -72,7 +85,17 @@ pub fn run() { handle, "File", true, - &[&close_window_item, &quit_item], + &[ + &new_agent_item, + &new_worktree_agent_item, + &new_clone_agent_item, + &PredefinedMenuItem::separator(handle)?, + &add_workspace_item, + &settings_item, + &PredefinedMenuItem::separator(handle)?, + &close_window_item, + &quit_item, + ], )? }; #[cfg(not(target_os = "linux"))] @@ -81,6 +104,13 @@ pub fn run() { "File", true, &[ + &new_agent_item, + &new_worktree_agent_item, + &new_clone_agent_item, + &PredefinedMenuItem::separator(handle)?, + &add_workspace_item, + &settings_item, + &PredefinedMenuItem::separator(handle)?, &PredefinedMenuItem::close_window(handle, None)?, #[cfg(not(target_os = "macos"))] &PredefinedMenuItem::quit(handle, None)?, @@ -194,6 +224,21 @@ pub fn run() { "check_for_updates" => { let _ = app.emit("updater-check", ()); } + "file_new_agent" => { + let _ = app.emit("menu-new-agent", ()); + } + "file_new_worktree_agent" => { + let _ = app.emit("menu-new-worktree-agent", ()); + } + "file_new_clone_agent" => { + let _ = app.emit("menu-new-clone-agent", ()); + } + "file_add_workspace" => { + let _ = app.emit("menu-add-workspace", ()); + } + "file_open_settings" => { + let _ = app.emit("menu-open-settings", ()); + } "file_close_window" | "window_close" => { if let Some(window) = app.get_webview_window("main") { let _ = window.close(); diff --git a/src/App.tsx b/src/App.tsx index b0d4be409..1acbfdc1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -93,7 +93,14 @@ import { playNotificationSound } from "./utils/notificationSounds"; import { pickWorkspacePath, } from "./services/tauri"; -import { subscribeUpdaterCheck } from "./services/events"; +import { + subscribeMenuAddWorkspace, + subscribeMenuNewAgent, + subscribeMenuNewCloneAgent, + subscribeMenuNewWorktreeAgent, + subscribeMenuOpenSettings, + subscribeUpdaterCheck, +} from "./services/events"; import type { AccessMode, GitHubPullRequest, @@ -1037,16 +1044,7 @@ function MainApp() { listThreadsForWorkspace }); - useNewAgentShortcut({ - isEnabled: Boolean(activeWorkspace), - onTrigger: () => { - if (activeWorkspace) { - void handleAddAgent(activeWorkspace); - } - }, - }); - - async function handleAddWorkspace() { + const handleAddWorkspace = useCallback(async () => { try { const workspace = await addWorkspace(); if (workspace) { @@ -1066,34 +1064,56 @@ function MainApp() { }); alert(`Failed to add workspace.\n\n${message}`); } - } + }, [addDebugEntry, addWorkspace, isCompact, setActiveTab, setActiveThreadId]); + const handleAddAgent = useCallback( + async (workspace: (typeof workspaces)[number]) => { + exitDiffView(); + selectWorkspace(workspace.id); + if (!workspace.connected) { + await connectWorkspace(workspace); + } + await startThreadForWorkspace(workspace.id); + if (isCompact) { + setActiveTab("codex"); + } + // Focus the composer input after creating the agent + setTimeout(() => composerInputRef.current?.focus(), 0); + }, + [ + connectWorkspace, + exitDiffView, + isCompact, + selectWorkspace, + setActiveTab, + startThreadForWorkspace, + ], + ); - async function handleAddAgent(workspace: (typeof workspaces)[number]) { - exitDiffView(); - selectWorkspace(workspace.id); - if (!workspace.connected) { - await connectWorkspace(workspace); - } - await startThreadForWorkspace(workspace.id); - if (isCompact) { - setActiveTab("codex"); - } - // Focus the composer input after creating the agent - setTimeout(() => composerInputRef.current?.focus(), 0); - } + const handleAddWorktreeAgent = useCallback( + async (workspace: (typeof workspaces)[number]) => { + exitDiffView(); + openWorktreePrompt(workspace); + }, + [exitDiffView, openWorktreePrompt], + ); - async function handleAddWorktreeAgent( - workspace: (typeof workspaces)[number] - ) { - exitDiffView(); - openWorktreePrompt(workspace); - } + const handleAddCloneAgent = useCallback( + async (workspace: (typeof workspaces)[number]) => { + exitDiffView(); + openClonePrompt(workspace); + }, + [exitDiffView, openClonePrompt], + ); - async function handleAddCloneAgent(workspace: (typeof workspaces)[number]) { - exitDiffView(); - openClonePrompt(workspace); - } + useNewAgentShortcut({ + isEnabled: Boolean(activeWorkspace), + onTrigger: () => { + if (activeWorkspace) { + void handleAddAgent(activeWorkspace); + } + }, + }); function handleSelectDiff(path: string) { setSelectedDiffPath(path); @@ -1167,6 +1187,87 @@ function MainApp() { [], ); + useEffect(() => { + let unlistenNewAgent: (() => void) | null = null; + let unlistenNewWorktree: (() => void) | null = null; + let unlistenNewClone: (() => void) | null = null; + let unlistenAddWorkspace: (() => void) | null = null; + let unlistenOpenSettings: (() => void) | null = null; + const baseWorkspace = activeParentWorkspace ?? activeWorkspace; + + subscribeMenuNewAgent(() => { + if (activeWorkspace) { + void handleAddAgent(activeWorkspace); + } + }) + .then((handler) => { + unlistenNewAgent = handler; + }) + .catch(() => {}); + + subscribeMenuNewWorktreeAgent(() => { + if (baseWorkspace) { + void handleAddWorktreeAgent(baseWorkspace); + } + }) + .then((handler) => { + unlistenNewWorktree = handler; + }) + .catch(() => {}); + + subscribeMenuNewCloneAgent(() => { + if (baseWorkspace) { + void handleAddCloneAgent(baseWorkspace); + } + }) + .then((handler) => { + unlistenNewClone = handler; + }) + .catch(() => {}); + + subscribeMenuAddWorkspace(() => { + void handleAddWorkspace(); + }) + .then((handler) => { + unlistenAddWorkspace = handler; + }) + .catch(() => {}); + + subscribeMenuOpenSettings(() => { + handleOpenSettings(); + }) + .then((handler) => { + unlistenOpenSettings = handler; + }) + .catch(() => {}); + + return () => { + if (unlistenNewAgent) { + unlistenNewAgent(); + } + if (unlistenNewWorktree) { + unlistenNewWorktree(); + } + if (unlistenNewClone) { + unlistenNewClone(); + } + if (unlistenAddWorkspace) { + unlistenAddWorkspace(); + } + if (unlistenOpenSettings) { + unlistenOpenSettings(); + } + }; + }, [ + activeParentWorkspace, + activeWorkspace, + handleAddAgent, + handleAddCloneAgent, + handleAddWorkspace, + handleAddWorktreeAgent, + handleOpenSettings, + ]); + const orderValue = (entry: WorkspaceInfo) => typeof entry.settings.sortOrder === "number" ? entry.settings.sortOrder diff --git a/src/services/events.ts b/src/services/events.ts index 98a593bfa..2d6679ba6 100644 --- a/src/services/events.ts +++ b/src/services/events.ts @@ -48,3 +48,43 @@ export async function subscribeUpdaterCheck( onEvent(); }); } + +export async function subscribeMenuNewAgent( + onEvent: () => void, +): Promise { + return listen("menu-new-agent", () => { + onEvent(); + }); +} + +export async function subscribeMenuNewWorktreeAgent( + onEvent: () => void, +): Promise { + return listen("menu-new-worktree-agent", () => { + onEvent(); + }); +} + +export async function subscribeMenuNewCloneAgent( + onEvent: () => void, +): Promise { + return listen("menu-new-clone-agent", () => { + onEvent(); + }); +} + +export async function subscribeMenuAddWorkspace( + onEvent: () => void, +): Promise { + return listen("menu-add-workspace", () => { + onEvent(); + }); +} + +export async function subscribeMenuOpenSettings( + onEvent: () => void, +): Promise { + return listen("menu-open-settings", () => { + onEvent(); + }); +} From 26ecf034ac32f4b286268fb80fff74016e51ac1c Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 20 Jan 2026 09:00:01 +0100 Subject: [PATCH 2/2] Update lib.rs --- src-tauri/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e586dfe00..948d5e4e7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -46,6 +46,8 @@ pub fn run() { let check_updates_item = MenuItemBuilder::with_id("check_for_updates", "Check for Updates...") .build(handle)?; + let settings_item = + MenuItemBuilder::with_id("file_open_settings", "Settings...").build(handle)?; let app_menu = Submenu::with_items( handle, app_name.clone(), @@ -53,6 +55,7 @@ pub fn run() { &[ &about_item, &check_updates_item, + &settings_item, &PredefinedMenuItem::separator(handle)?, &PredefinedMenuItem::services(handle, None)?, &PredefinedMenuItem::separator(handle)?, @@ -73,8 +76,6 @@ pub fn run() { .build(handle)?; let add_workspace_item = MenuItemBuilder::with_id("file_add_workspace", "Add Workspace...").build(handle)?; - let settings_item = - MenuItemBuilder::with_id("file_open_settings", "Settings...").build(handle)?; #[cfg(target_os = "linux")] let file_menu = { @@ -91,7 +92,6 @@ pub fn run() { &new_clone_agent_item, &PredefinedMenuItem::separator(handle)?, &add_workspace_item, - &settings_item, &PredefinedMenuItem::separator(handle)?, &close_window_item, &quit_item, @@ -109,7 +109,6 @@ pub fn run() { &new_clone_agent_item, &PredefinedMenuItem::separator(handle)?, &add_workspace_item, - &settings_item, &PredefinedMenuItem::separator(handle)?, &PredefinedMenuItem::close_window(handle, None)?, #[cfg(not(target_os = "macos"))]