mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-19 10:43:08 +00:00
Fix: Dev server detection bug fixes. Settings sync bug fixes. Cli provider fixes. Terminal background/foreground colors (#791)
* Changes from fix/dev-server-state-bug * feat: Add configurable max turns setting with user overrides. Address pr comments * fix: Update default behaviors and improve state management across server and UI * feat: Extract branch sync logic to separate service. Fix settings sync bug. Address pr comments * refactor: Extract magic numbers to named constants and improve branch tracking logic - Add DEFAULT_MAX_TURNS (1000) and MAX_ALLOWED_TURNS (2000) constants to settings-helpers - Replace hardcoded 1000 values with DEFAULT_MAX_TURNS constant throughout codebase - Improve max turns validation with explicit Number.isFinite check - Update getTrackingBranch to split on first slash instead of last for better remote parsing - Change isBranchCheckedOut return type from boolean to string|null to return worktree path - Add comments explaining skipFetch parameter in worktree creation - Fix cleanup order in AgentExecutor finally block to run before logging ``` * feat: Add comment refresh and improve model sync in PR dialog
This commit is contained in:
@@ -369,6 +369,7 @@ const initialState: AppState = {
|
||||
defaultFeatureModel: DEFAULT_GLOBAL_SETTINGS.defaultFeatureModel,
|
||||
defaultThinkingLevel: DEFAULT_GLOBAL_SETTINGS.defaultThinkingLevel ?? 'none',
|
||||
defaultReasoningEffort: DEFAULT_GLOBAL_SETTINGS.defaultReasoningEffort ?? 'none',
|
||||
defaultMaxTurns: DEFAULT_GLOBAL_SETTINGS.defaultMaxTurns ?? 1000,
|
||||
pendingPlanApproval: null,
|
||||
claudeRefreshInterval: 60,
|
||||
claudeUsage: null,
|
||||
@@ -991,7 +992,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
const key = get().getWorktreeKey(projectId, branchName);
|
||||
set((state) => {
|
||||
const current = state.autoModeByWorktree[key] || {
|
||||
isRunning: true,
|
||||
isRunning: false,
|
||||
runningTasks: [],
|
||||
branchName,
|
||||
};
|
||||
@@ -1109,7 +1110,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { skipVerificationInAutoMode: enabled });
|
||||
await httpApi.settings.updateGlobal({ skipVerificationInAutoMode: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync skipVerificationInAutoMode:', error);
|
||||
}
|
||||
@@ -1119,7 +1120,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { enableAiCommitMessages: enabled });
|
||||
await httpApi.settings.updateGlobal({ enableAiCommitMessages: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync enableAiCommitMessages:', error);
|
||||
}
|
||||
@@ -1129,7 +1130,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { mergePostAction: action });
|
||||
await httpApi.settings.updateGlobal({ mergePostAction: action });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync mergePostAction:', error);
|
||||
}
|
||||
@@ -1139,7 +1140,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { planUseSelectedWorktreeBranch: enabled });
|
||||
await httpApi.settings.updateGlobal({ planUseSelectedWorktreeBranch: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync planUseSelectedWorktreeBranch:', error);
|
||||
}
|
||||
@@ -1149,7 +1150,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { addFeatureUseSelectedWorktreeBranch: enabled });
|
||||
await httpApi.settings.updateGlobal({ addFeatureUseSelectedWorktreeBranch: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync addFeatureUseSelectedWorktreeBranch:', error);
|
||||
}
|
||||
@@ -1222,7 +1223,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { phaseModels: get().phaseModels });
|
||||
await httpApi.settings.updateGlobal({ phaseModels: get().phaseModels });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync phase model:', error);
|
||||
}
|
||||
@@ -1234,7 +1235,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { phaseModels: get().phaseModels });
|
||||
await httpApi.settings.updateGlobal({ phaseModels: get().phaseModels });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync phase models:', error);
|
||||
}
|
||||
@@ -1244,7 +1245,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { phaseModels: DEFAULT_PHASE_MODELS });
|
||||
await httpApi.settings.updateGlobal({ phaseModels: DEFAULT_PHASE_MODELS });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync phase models reset:', error);
|
||||
}
|
||||
@@ -1279,7 +1280,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ codexAutoLoadAgents: enabled });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { codexAutoLoadAgents: enabled });
|
||||
await httpApi.settings.updateGlobal({ codexAutoLoadAgents: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync codexAutoLoadAgents:', error);
|
||||
}
|
||||
@@ -1288,7 +1289,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ codexSandboxMode: mode });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { codexSandboxMode: mode });
|
||||
await httpApi.settings.updateGlobal({ codexSandboxMode: mode });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync codexSandboxMode:', error);
|
||||
}
|
||||
@@ -1297,7 +1298,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ codexApprovalPolicy: policy });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { codexApprovalPolicy: policy });
|
||||
await httpApi.settings.updateGlobal({ codexApprovalPolicy: policy });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync codexApprovalPolicy:', error);
|
||||
}
|
||||
@@ -1306,7 +1307,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ codexEnableWebSearch: enabled });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { codexEnableWebSearch: enabled });
|
||||
await httpApi.settings.updateGlobal({ codexEnableWebSearch: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync codexEnableWebSearch:', error);
|
||||
}
|
||||
@@ -1315,7 +1316,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ codexEnableImages: enabled });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { codexEnableImages: enabled });
|
||||
await httpApi.settings.updateGlobal({ codexEnableImages: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync codexEnableImages:', error);
|
||||
}
|
||||
@@ -1375,7 +1376,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ autoLoadClaudeMd: enabled });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { autoLoadClaudeMd: enabled });
|
||||
await httpApi.settings.updateGlobal({ autoLoadClaudeMd: enabled });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync autoLoadClaudeMd:', error);
|
||||
}
|
||||
@@ -1384,7 +1385,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ skipSandboxWarning: skip });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { skipSandboxWarning: skip });
|
||||
await httpApi.settings.updateGlobal({ skipSandboxWarning: skip });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync skipSandboxWarning:', error);
|
||||
}
|
||||
@@ -1407,7 +1408,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ promptCustomization: customization });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { promptCustomization: customization });
|
||||
await httpApi.settings.updateGlobal({ promptCustomization: customization });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync prompt customization:', error);
|
||||
}
|
||||
@@ -1423,7 +1424,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', {
|
||||
await httpApi.settings.updateGlobal({
|
||||
claudeCompatibleProviders: get().claudeCompatibleProviders,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1438,7 +1439,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', {
|
||||
await httpApi.settings.updateGlobal({
|
||||
claudeCompatibleProviders: get().claudeCompatibleProviders,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1451,7 +1452,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', {
|
||||
await httpApi.settings.updateGlobal({
|
||||
claudeCompatibleProviders: get().claudeCompatibleProviders,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1462,7 +1463,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ claudeCompatibleProviders: providers });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { claudeCompatibleProviders: providers });
|
||||
await httpApi.settings.updateGlobal({ claudeCompatibleProviders: providers });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync Claude-compatible providers:', error);
|
||||
}
|
||||
@@ -1475,7 +1476,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', {
|
||||
await httpApi.settings.updateGlobal({
|
||||
claudeCompatibleProviders: get().claudeCompatibleProviders,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1490,7 +1491,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { claudeApiProfiles: get().claudeApiProfiles });
|
||||
await httpApi.settings.updateGlobal({ claudeApiProfiles: get().claudeApiProfiles });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync Claude API profiles:', error);
|
||||
}
|
||||
@@ -1503,7 +1504,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { claudeApiProfiles: get().claudeApiProfiles });
|
||||
await httpApi.settings.updateGlobal({ claudeApiProfiles: get().claudeApiProfiles });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync Claude API profiles:', error);
|
||||
}
|
||||
@@ -1516,7 +1517,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', {
|
||||
await httpApi.settings.updateGlobal({
|
||||
claudeApiProfiles: get().claudeApiProfiles,
|
||||
activeClaudeApiProfileId: get().activeClaudeApiProfileId,
|
||||
});
|
||||
@@ -1528,7 +1529,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ activeClaudeApiProfileId: id });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { activeClaudeApiProfileId: id });
|
||||
await httpApi.settings.updateGlobal({ activeClaudeApiProfileId: id });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync active Claude API profile:', error);
|
||||
}
|
||||
@@ -1537,7 +1538,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
set({ claudeApiProfiles: profiles });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { claudeApiProfiles: profiles });
|
||||
await httpApi.settings.updateGlobal({ claudeApiProfiles: profiles });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync Claude API profiles:', error);
|
||||
}
|
||||
@@ -1947,6 +1948,16 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
terminalState: { ...state.terminalState, openTerminalMode: mode },
|
||||
})),
|
||||
|
||||
setTerminalBackgroundColor: (color) =>
|
||||
set((state) => ({
|
||||
terminalState: { ...state.terminalState, customBackgroundColor: color },
|
||||
})),
|
||||
|
||||
setTerminalForegroundColor: (color) =>
|
||||
set((state) => ({
|
||||
terminalState: { ...state.terminalState, customForegroundColor: color },
|
||||
})),
|
||||
|
||||
addTerminalTab: (name) => {
|
||||
const newTabId = `tab-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const tabNumber = get().terminalState.tabs.length + 1;
|
||||
@@ -2341,7 +2352,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { defaultThinkingLevel: level });
|
||||
await httpApi.settings.updateGlobal({ defaultThinkingLevel: level });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync defaultThinkingLevel:', error);
|
||||
}
|
||||
@@ -2352,12 +2363,27 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.put('/api/settings', { defaultReasoningEffort: effort });
|
||||
await httpApi.settings.updateGlobal({ defaultReasoningEffort: effort });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync defaultReasoningEffort:', error);
|
||||
}
|
||||
},
|
||||
|
||||
setDefaultMaxTurns: async (maxTurns: number) => {
|
||||
// Guard against NaN/Infinity before flooring and clamping
|
||||
const safeValue = Number.isFinite(maxTurns) ? maxTurns : 1;
|
||||
// Clamp to valid range
|
||||
const clamped = Math.max(1, Math.min(2000, Math.floor(safeValue)));
|
||||
set({ defaultMaxTurns: clamped });
|
||||
// Sync to server
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({ defaultMaxTurns: clamped });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync defaultMaxTurns:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// Plan Approval actions
|
||||
setPendingPlanApproval: (approval) => set({ pendingPlanApproval: approval }),
|
||||
|
||||
|
||||
@@ -18,4 +18,6 @@ export const defaultTerminalState: TerminalState = {
|
||||
maxSessions: 100,
|
||||
lastActiveProjectPath: null,
|
||||
openTerminalMode: 'newTab',
|
||||
customBackgroundColor: null,
|
||||
customForegroundColor: null,
|
||||
};
|
||||
|
||||
@@ -182,6 +182,9 @@ export interface AppState {
|
||||
defaultThinkingLevel: ThinkingLevel;
|
||||
defaultReasoningEffort: ReasoningEffort;
|
||||
|
||||
// Default max turns for agent execution (1-2000)
|
||||
defaultMaxTurns: number;
|
||||
|
||||
// Cursor CLI Settings (global)
|
||||
enabledCursorModels: CursorModelId[]; // Which Cursor models are available in feature modal
|
||||
cursorDefaultModel: CursorModelId; // Default Cursor model selection
|
||||
@@ -564,6 +567,7 @@ export interface AppActions {
|
||||
toggleFavoriteModel: (modelId: string) => void;
|
||||
setDefaultThinkingLevel: (level: ThinkingLevel) => void;
|
||||
setDefaultReasoningEffort: (effort: ReasoningEffort) => void;
|
||||
setDefaultMaxTurns: (maxTurns: number) => void;
|
||||
|
||||
// Cursor CLI Settings actions
|
||||
setEnabledCursorModels: (models: CursorModelId[]) => void;
|
||||
@@ -708,6 +712,8 @@ export interface AppActions {
|
||||
setTerminalMaxSessions: (maxSessions: number) => void;
|
||||
setTerminalLastActiveProjectPath: (projectPath: string | null) => void;
|
||||
setOpenTerminalMode: (mode: 'newTab' | 'split') => void;
|
||||
setTerminalBackgroundColor: (color: string | null) => void;
|
||||
setTerminalForegroundColor: (color: string | null) => void;
|
||||
addTerminalTab: (name?: string) => string;
|
||||
removeTerminalTab: (tabId: string) => void;
|
||||
setActiveTerminalTab: (tabId: string) => void;
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface TerminalState {
|
||||
maxSessions: number; // Maximum concurrent terminal sessions (server setting)
|
||||
lastActiveProjectPath: string | null; // Last project path to detect route changes vs project switches
|
||||
openTerminalMode: 'newTab' | 'split'; // How to open terminals from "Open in Terminal" action
|
||||
customBackgroundColor: string | null; // Custom background color override (hex color string, null = use theme default)
|
||||
customForegroundColor: string | null; // Custom foreground/text color override (hex color string, null = use theme default)
|
||||
}
|
||||
|
||||
// Persisted terminal layout - now includes sessionIds for reconnection
|
||||
@@ -79,4 +81,6 @@ export interface PersistedTerminalSettings {
|
||||
lineHeight: number;
|
||||
maxSessions: number;
|
||||
openTerminalMode: 'newTab' | 'split';
|
||||
customBackgroundColor: string | null; // Custom background color override (hex color string, null = use theme default)
|
||||
customForegroundColor: string | null; // Custom foreground/text color override (hex color string, null = use theme default)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user