feat: add auto-create worktree setting and related UI components
This commit is contained in:
@@ -257,6 +257,9 @@ fn sanitize_settings_update(payload: &Value) -> Value {
|
|||||||
if let Some(Value::Bool(b)) = obj.get("queueModeEnabled") {
|
if let Some(Value::Bool(b)) = obj.get("queueModeEnabled") {
|
||||||
result_obj.insert("queueModeEnabled".to_string(), json!(b));
|
result_obj.insert("queueModeEnabled".to_string(), json!(b));
|
||||||
}
|
}
|
||||||
|
if let Some(Value::Bool(b)) = obj.get("autoCreateWorktree") {
|
||||||
|
result_obj.insert("autoCreateWorktree".to_string(), json!(b));
|
||||||
|
}
|
||||||
|
|
||||||
// Number fields
|
// Number fields
|
||||||
if let Some(Value::Number(n)) = obj.get("autoDeleteAfterDays") {
|
if let Some(Value::Number(n)) = obj.get("autoDeleteAfterDays") {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { updateDesktopSettings } from '@/lib/persistence';
|
|||||||
import { isDesktopRuntime, getDesktopSettings } from '@/lib/desktop';
|
import { isDesktopRuntime, getDesktopSettings } from '@/lib/desktop';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||||
|
import { getModifierLabel } from '@/lib/utils';
|
||||||
|
|
||||||
export const DefaultsSettings: React.FC = () => {
|
export const DefaultsSettings: React.FC = () => {
|
||||||
const setProvider = useConfigStore((state) => state.setProvider);
|
const setProvider = useConfigStore((state) => state.setProvider);
|
||||||
@@ -14,6 +15,8 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
const setAgent = useConfigStore((state) => state.setAgent);
|
const setAgent = useConfigStore((state) => state.setAgent);
|
||||||
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
|
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
|
||||||
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
|
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
|
||||||
|
const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree);
|
||||||
|
const setSettingsAutoCreateWorktree = useConfigStore((state) => state.setSettingsAutoCreateWorktree);
|
||||||
const providers = useConfigStore((state) => state.providers);
|
const providers = useConfigStore((state) => state.providers);
|
||||||
|
|
||||||
const [defaultModel, setDefaultModel] = React.useState<string | undefined>();
|
const [defaultModel, setDefaultModel] = React.useState<string | undefined>();
|
||||||
@@ -126,6 +129,18 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [setAgent, setSettingsDefaultAgent]);
|
}, [setAgent, setSettingsDefaultAgent]);
|
||||||
|
|
||||||
|
const handleAutoWorktreeChange = React.useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const enabled = e.target.checked;
|
||||||
|
setSettingsAutoCreateWorktree(enabled);
|
||||||
|
try {
|
||||||
|
await updateDesktopSettings({
|
||||||
|
autoCreateWorktree: enabled,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save auto create worktree setting:', error);
|
||||||
|
}
|
||||||
|
}, [setSettingsAutoCreateWorktree]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -134,14 +149,13 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="typography-ui-header font-semibold text-foreground">Default model & agent</h3>
|
<h3 className="typography-ui-header font-semibold text-foreground">Session Defaults</h3>
|
||||||
<Tooltip delayDuration={1000}>
|
<Tooltip delayDuration={1000}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||||
Set the default model and agent for new sessions.<br />
|
Configure default behaviors for new sessions.
|
||||||
When not set, uses agent's preferred model or opencode/big-pickle as fallback.
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,6 +188,25 @@ export const DefaultsSettings: React.FC = () => {
|
|||||||
{defaultAgent && <span className="text-foreground">{defaultAgent}</span>}
|
{defaultAgent && <span className="text-foreground">{defaultAgent}</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3.5 w-3.5 accent-primary"
|
||||||
|
checked={settingsAutoCreateWorktree}
|
||||||
|
onChange={handleAutoWorktreeChange}
|
||||||
|
/>
|
||||||
|
<span className="typography-ui-label text-foreground">
|
||||||
|
Always create worktree for new sessions
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p className="typography-meta text-muted-foreground pl-5.5 mt-1">
|
||||||
|
{settingsAutoCreateWorktree
|
||||||
|
? `New session (Worktree): ${getModifierLabel()} + N • New session (Standard): Shift + ${getModifierLabel()} + N`
|
||||||
|
: `New session (Standard): ${getModifierLabel()} + N • New session (Worktree): Shift + ${getModifierLabel()} + N`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -698,6 +698,18 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
const handleDeleteSession = React.useCallback(
|
const handleDeleteSession = React.useCallback(
|
||||||
async (session: Session) => {
|
async (session: Session) => {
|
||||||
const descendants = collectDescendants(session.id);
|
const descendants = collectDescendants(session.id);
|
||||||
|
|
||||||
|
// Check if this is a worktree session - if so, show confirmation dialog
|
||||||
|
const worktree = worktreeMetadata.get(session.id);
|
||||||
|
if (worktree) {
|
||||||
|
sessionEvents.requestDelete({
|
||||||
|
sessions: [session, ...descendants],
|
||||||
|
mode: 'worktree',
|
||||||
|
worktree,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (descendants.length === 0) {
|
if (descendants.length === 0) {
|
||||||
|
|
||||||
const success = await deleteSession(session.id);
|
const success = await deleteSession(session.id);
|
||||||
@@ -718,7 +730,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[collectDescendants, deleteSession, deleteSessions],
|
[collectDescendants, deleteSession, deleteSessions, worktreeMetadata],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreateSessionInGroup = React.useCallback(
|
const handleCreateSessionInGroup = React.useCallback(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { useSessionStore } from '@/stores/useSessionStore';
|
import { useSessionStore } from '@/stores/useSessionStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||||
import { useDeviceInfo } from '@/lib/device';
|
import { useDeviceInfo } from '@/lib/device';
|
||||||
import { RiAddLine, RiChatAi3Line, RiCheckLine, RiCodeLine, RiComputerLine, RiGitBranchLine, RiLayoutLeftLine, RiMoonLine, RiQuestionLine, RiRestartLine, RiSettings3Line, RiSunLine, RiTerminalBoxLine, RiTimeLine } from '@remixicon/react';
|
import { RiAddLine, RiChatAi3Line, RiCheckLine, RiCodeLine, RiComputerLine, RiGitBranchLine, RiLayoutLeftLine, RiMoonLine, RiQuestionLine, RiRestartLine, RiSettings3Line, RiSunLine, RiTerminalBoxLine, RiTimeLine } from '@remixicon/react';
|
||||||
@@ -37,6 +38,8 @@ export const CommandPalette: React.FC = () => {
|
|||||||
getSessionsByDirectory,
|
getSessionsByDirectory,
|
||||||
} = useSessionStore();
|
} = useSessionStore();
|
||||||
|
|
||||||
|
const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree);
|
||||||
|
|
||||||
const { currentDirectory } = useDirectoryStore();
|
const { currentDirectory } = useDirectoryStore();
|
||||||
const { themeMode, setThemeMode } = useThemeSystem();
|
const { themeMode, setThemeMode } = useThemeSystem();
|
||||||
|
|
||||||
@@ -133,12 +136,16 @@ export const CommandPalette: React.FC = () => {
|
|||||||
<CommandItem onSelect={handleCreateSession}>
|
<CommandItem onSelect={handleCreateSession}>
|
||||||
<RiAddLine className="mr-2 h-4 w-4" />
|
<RiAddLine className="mr-2 h-4 w-4" />
|
||||||
<span>New Session</span>
|
<span>New Session</span>
|
||||||
<CommandShortcut>{getModifierLabel()} + N</CommandShortcut>
|
<CommandShortcut>
|
||||||
|
{settingsAutoCreateWorktree ? `Shift + ${getModifierLabel()} + N` : `${getModifierLabel()} + N`}
|
||||||
|
</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem onSelect={handleCreateWorktreeSession}>
|
<CommandItem onSelect={handleCreateWorktreeSession}>
|
||||||
<RiGitBranchLine className="mr-2 h-4 w-4" />
|
<RiGitBranchLine className="mr-2 h-4 w-4" />
|
||||||
<span>New Session with Worktree</span>
|
<span>New Session with Worktree</span>
|
||||||
<CommandShortcut>Shift + {getModifierLabel()} + N</CommandShortcut>
|
<CommandShortcut>
|
||||||
|
{settingsAutoCreateWorktree ? `${getModifierLabel()} + N` : `Shift + ${getModifierLabel()} + N`}
|
||||||
|
</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem onSelect={handleShowHelp}>
|
<CommandItem onSelect={handleShowHelp}>
|
||||||
<RiQuestionLine className="mr-2 h-4 w-4" />
|
<RiQuestionLine className="mr-2 h-4 w-4" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { useUIStore } from "@/stores/useUIStore";
|
import { useUIStore } from "@/stores/useUIStore";
|
||||||
|
import { useConfigStore } from "@/stores/useConfigStore";
|
||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddLine,
|
||||||
RiArrowUpSLine,
|
RiArrowUpSLine,
|
||||||
@@ -82,6 +83,7 @@ type ShortcutSection = {
|
|||||||
|
|
||||||
export const HelpDialog: React.FC = () => {
|
export const HelpDialog: React.FC = () => {
|
||||||
const { isHelpDialogOpen, setHelpDialogOpen } = useUIStore();
|
const { isHelpDialogOpen, setHelpDialogOpen } = useUIStore();
|
||||||
|
const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree);
|
||||||
|
|
||||||
const mod = getModifierLabel();
|
const mod = getModifierLabel();
|
||||||
|
|
||||||
@@ -116,13 +118,13 @@ export const HelpDialog: React.FC = () => {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
keys: [`${mod} + N`],
|
keys: [`${mod} + N`],
|
||||||
description: "Create New Session",
|
description: settingsAutoCreateWorktree ? "Create new session in worktree" : "Create New Session",
|
||||||
icon: RiAddLine,
|
icon: settingsAutoCreateWorktree ? RiGitBranchLine : RiAddLine,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keys: [`Shift + ${mod} + N`],
|
keys: [`Shift + ${mod} + N`],
|
||||||
description: "Open Worktree Creator",
|
description: settingsAutoCreateWorktree ? "Create New Session" : "Create new session in worktree",
|
||||||
icon: RiGitBranchLine,
|
icon: settingsAutoCreateWorktree ? RiAddLine : RiGitBranchLine,
|
||||||
},
|
},
|
||||||
{ keys: [`${mod} + I`], description: "Focus Chat Input", icon: RiText },
|
{ keys: [`${mod} + I`], description: "Focus Chat Input", icon: RiText },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useAssistantStatus } from '@/hooks/useAssistantStatus';
|
|||||||
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||||
import { hasModifier } from '@/lib/utils';
|
import { hasModifier } from '@/lib/utils';
|
||||||
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
||||||
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
|
|
||||||
export const useKeyboardShortcuts = () => {
|
export const useKeyboardShortcuts = () => {
|
||||||
const { openNewSessionDraft, abortCurrentOperation, armAbortPrompt, clearAbortPrompt, currentSessionId } = useSessionStore();
|
const { openNewSessionDraft, abortCurrentOperation, armAbortPrompt, clearAbortPrompt, currentSessionId } = useSessionStore();
|
||||||
@@ -81,14 +82,20 @@ export const useKeyboardShortcuts = () => {
|
|||||||
|
|
||||||
if (hasModifier(e) && e.key.toLowerCase() === 'n') {
|
if (hasModifier(e) && e.key.toLowerCase() === 'n') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.shiftKey) {
|
|
||||||
// Shift+Cmd/Ctrl+N creates a new session with auto-generated worktree
|
const autoWorktree = useConfigStore.getState().settingsAutoCreateWorktree;
|
||||||
|
// If autoWorktree is true: Cmd+N -> Worktree, Cmd+Shift+N -> Standard
|
||||||
|
// If autoWorktree is false: Cmd+N -> Standard, Cmd+Shift+N -> Worktree
|
||||||
|
const shouldCreateWorktree = autoWorktree ? !e.shiftKey : e.shiftKey;
|
||||||
|
|
||||||
|
if (shouldCreateWorktree) {
|
||||||
|
// Create new session with auto-generated worktree
|
||||||
setActiveMainTab('chat');
|
setActiveMainTab('chat');
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
createWorktreeSession();
|
createWorktreeSession();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Cmd/Ctrl+N opens a new session without worktree
|
// Open a new session without worktree
|
||||||
setActiveMainTab('chat');
|
setActiveMainTab('chat');
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
openNewSessionDraft();
|
openNewSessionDraft();
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export type DesktopSettings = {
|
|||||||
autoDeleteAfterDays?: number;
|
autoDeleteAfterDays?: number;
|
||||||
defaultModel?: string; // format: "provider/model"
|
defaultModel?: string; // format: "provider/model"
|
||||||
defaultAgent?: string;
|
defaultAgent?: string;
|
||||||
|
autoCreateWorktree?: boolean;
|
||||||
queueModeEnabled?: boolean;
|
queueModeEnabled?: boolean;
|
||||||
|
|
||||||
// User-added skills catalogs (persisted to ~/.config/openchamber/settings.json)
|
// User-added skills catalogs (persisted to ~/.config/openchamber/settings.json)
|
||||||
|
|||||||
@@ -255,6 +255,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|||||||
if (typeof candidate.defaultAgent === 'string' && candidate.defaultAgent.length > 0) {
|
if (typeof candidate.defaultAgent === 'string' && candidate.defaultAgent.length > 0) {
|
||||||
result.defaultAgent = candidate.defaultAgent;
|
result.defaultAgent = candidate.defaultAgent;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.autoCreateWorktree === 'boolean') {
|
||||||
|
result.autoCreateWorktree = candidate.autoCreateWorktree;
|
||||||
|
}
|
||||||
if (typeof candidate.queueModeEnabled === 'boolean') {
|
if (typeof candidate.queueModeEnabled === 'boolean') {
|
||||||
result.queueModeEnabled = candidate.queueModeEnabled;
|
result.queueModeEnabled = candidate.queueModeEnabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { toast } from 'sonner';
|
|||||||
import { useSessionStore } from '@/stores/useSessionStore';
|
import { useSessionStore } from '@/stores/useSessionStore';
|
||||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
|
import { useContextStore } from '@/stores/contextStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
import { checkIsGitRepository } from '@/lib/gitApi';
|
import { checkIsGitRepository } from '@/lib/gitApi';
|
||||||
import { generateUniqueBranchName } from '@/lib/git/branchNameGenerator';
|
import { generateUniqueBranchName } from '@/lib/git/branchNameGenerator';
|
||||||
@@ -116,11 +117,57 @@ export async function createWorktreeSession(): Promise<{ id: string } | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the session
|
// Initialize the session
|
||||||
const agents = useConfigStore.getState().agents;
|
const configState = useConfigStore.getState();
|
||||||
|
const agents = configState.agents;
|
||||||
sessionStore.initializeNewOpenChamberSession(session.id, agents);
|
sessionStore.initializeNewOpenChamberSession(session.id, agents);
|
||||||
sessionStore.setSessionDirectory(session.id, metadata.path);
|
sessionStore.setSessionDirectory(session.id, metadata.path);
|
||||||
sessionStore.setWorktreeMetadata(session.id, createdMetadata);
|
sessionStore.setWorktreeMetadata(session.id, createdMetadata);
|
||||||
|
|
||||||
|
// Apply default agent and model settings
|
||||||
|
try {
|
||||||
|
const visibleAgents = configState.getVisibleAgents();
|
||||||
|
let agentName: string | undefined;
|
||||||
|
|
||||||
|
// Priority: settingsDefaultAgent → build → first visible
|
||||||
|
if (configState.settingsDefaultAgent) {
|
||||||
|
const settingsAgent = visibleAgents.find((a) => a.name === configState.settingsDefaultAgent);
|
||||||
|
if (settingsAgent) {
|
||||||
|
agentName = settingsAgent.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!agentName) {
|
||||||
|
agentName =
|
||||||
|
visibleAgents.find((agent) => agent.name === 'build')?.name ||
|
||||||
|
visibleAgents[0]?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agentName) {
|
||||||
|
// 1. Update global UI state
|
||||||
|
configState.setAgent(agentName);
|
||||||
|
|
||||||
|
// 2. Persist to session context so it sticks after reload/switch
|
||||||
|
useContextStore.getState().saveSessionAgentSelection(session.id, agentName);
|
||||||
|
|
||||||
|
// 3. Handle default model for the agent if set in global settings
|
||||||
|
const settingsDefaultModel = configState.settingsDefaultModel;
|
||||||
|
if (settingsDefaultModel) {
|
||||||
|
const parts = settingsDefaultModel.split('/');
|
||||||
|
if (parts.length === 2) {
|
||||||
|
const [providerId, modelId] = parts;
|
||||||
|
// Validate model exists (optional, but good practice)
|
||||||
|
const modelMetadata = configState.getModelMetadata(providerId, modelId);
|
||||||
|
if (modelMetadata) {
|
||||||
|
useContextStore.getState().saveSessionModelSelection(session.id, providerId, modelId);
|
||||||
|
// Also save the specific agent's model preference for this session
|
||||||
|
useContextStore.getState().saveAgentModelForSession(session.id, agentName, providerId, modelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore errors setting default agent
|
||||||
|
}
|
||||||
|
|
||||||
// Update directory
|
// Update directory
|
||||||
useDirectoryStore.getState().setDirectory(metadata.path, { showOverlay: false });
|
useDirectoryStore.getState().setDirectory(metadata.path, { showOverlay: false });
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const FALLBACK_MODEL_ID = "big-pickle";
|
|||||||
interface OpenChamberDefaults {
|
interface OpenChamberDefaults {
|
||||||
defaultModel?: string;
|
defaultModel?: string;
|
||||||
defaultAgent?: string;
|
defaultAgent?: string;
|
||||||
|
autoCreateWorktree?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
||||||
@@ -33,6 +34,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
return {
|
return {
|
||||||
defaultModel: settings?.defaultModel,
|
defaultModel: settings?.defaultModel,
|
||||||
defaultAgent: settings?.defaultAgent,
|
defaultAgent: settings?.defaultAgent,
|
||||||
|
autoCreateWorktree: settings?.autoCreateWorktree,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
return {
|
return {
|
||||||
defaultModel: typeof data?.defaultModel === 'string' ? data.defaultModel : undefined,
|
defaultModel: typeof data?.defaultModel === 'string' ? data.defaultModel : undefined,
|
||||||
defaultAgent: typeof data?.defaultAgent === 'string' ? data.defaultAgent : undefined,
|
defaultAgent: typeof data?.defaultAgent === 'string' ? data.defaultAgent : undefined,
|
||||||
|
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -65,6 +68,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
return {
|
return {
|
||||||
defaultModel: typeof data?.defaultModel === 'string' ? data.defaultModel : undefined,
|
defaultModel: typeof data?.defaultModel === 'string' ? data.defaultModel : undefined,
|
||||||
defaultAgent: typeof data?.defaultAgent === 'string' ? data.defaultAgent : undefined,
|
defaultAgent: typeof data?.defaultAgent === 'string' ? data.defaultAgent : undefined,
|
||||||
|
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
@@ -351,6 +355,7 @@ interface ConfigStore {
|
|||||||
// OpenChamber settings-based defaults (take precedence over agent preferences)
|
// OpenChamber settings-based defaults (take precedence over agent preferences)
|
||||||
settingsDefaultModel: string | undefined; // format: "provider/model"
|
settingsDefaultModel: string | undefined; // format: "provider/model"
|
||||||
settingsDefaultAgent: string | undefined;
|
settingsDefaultAgent: string | undefined;
|
||||||
|
settingsAutoCreateWorktree: boolean;
|
||||||
|
|
||||||
activateDirectory: (directory: string | null | undefined) => Promise<void>;
|
activateDirectory: (directory: string | null | undefined) => Promise<void>;
|
||||||
|
|
||||||
@@ -362,6 +367,7 @@ interface ConfigStore {
|
|||||||
setSelectedProvider: (providerId: string) => void;
|
setSelectedProvider: (providerId: string) => void;
|
||||||
setSettingsDefaultModel: (model: string | undefined) => void;
|
setSettingsDefaultModel: (model: string | undefined) => void;
|
||||||
setSettingsDefaultAgent: (agent: string | undefined) => void;
|
setSettingsDefaultAgent: (agent: string | undefined) => void;
|
||||||
|
setSettingsAutoCreateWorktree: (enabled: boolean) => void;
|
||||||
saveAgentModelSelection: (agentName: string, providerId: string, modelId: string) => void;
|
saveAgentModelSelection: (agentName: string, providerId: string, modelId: string) => void;
|
||||||
getAgentModelSelection: (agentName: string) => { providerId: string; modelId: string } | null;
|
getAgentModelSelection: (agentName: string) => { providerId: string; modelId: string } | null;
|
||||||
checkConnection: () => Promise<boolean>;
|
checkConnection: () => Promise<boolean>;
|
||||||
@@ -402,6 +408,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
modelsMetadata: new Map<string, ModelMetadata>(),
|
modelsMetadata: new Map<string, ModelMetadata>(),
|
||||||
settingsDefaultModel: undefined,
|
settingsDefaultModel: undefined,
|
||||||
settingsDefaultAgent: undefined,
|
settingsDefaultAgent: undefined,
|
||||||
|
settingsAutoCreateWorktree: false,
|
||||||
|
|
||||||
activateDirectory: async (directory) => {
|
activateDirectory: async (directory) => {
|
||||||
const directoryKey = toDirectoryKey(directory);
|
const directoryKey = toDirectoryKey(directory);
|
||||||
@@ -730,6 +737,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
const nextState: Partial<ConfigStore> = {
|
const nextState: Partial<ConfigStore> = {
|
||||||
settingsDefaultModel: openChamberDefaults.defaultModel,
|
settingsDefaultModel: openChamberDefaults.defaultModel,
|
||||||
settingsDefaultAgent: openChamberDefaults.defaultAgent,
|
settingsDefaultAgent: openChamberDefaults.defaultAgent,
|
||||||
|
settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false,
|
||||||
directoryScoped: {
|
directoryScoped: {
|
||||||
...state.directoryScoped,
|
...state.directoryScoped,
|
||||||
[directoryKey]: nextSnapshot,
|
[directoryKey]: nextSnapshot,
|
||||||
@@ -1111,6 +1119,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
set({ settingsDefaultAgent: agent });
|
set({ settingsDefaultAgent: agent });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setSettingsAutoCreateWorktree: (enabled: boolean) => {
|
||||||
|
set({ settingsAutoCreateWorktree: enabled });
|
||||||
|
},
|
||||||
|
|
||||||
checkConnection: async () => {
|
checkConnection: async () => {
|
||||||
const maxAttempts = 5;
|
const maxAttempts = 5;
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
|
|||||||
@@ -599,6 +599,9 @@ const sanitizeSettingsUpdate = (payload) => {
|
|||||||
if (typeof candidate.queueModeEnabled === 'boolean') {
|
if (typeof candidate.queueModeEnabled === 'boolean') {
|
||||||
result.queueModeEnabled = candidate.queueModeEnabled;
|
result.queueModeEnabled = candidate.queueModeEnabled;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.autoCreateWorktree === 'boolean') {
|
||||||
|
result.autoCreateWorktree = candidate.autoCreateWorktree;
|
||||||
|
}
|
||||||
|
|
||||||
const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
|
const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
|
||||||
if (skillCatalogs) {
|
if (skillCatalogs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user