mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
refactor(tab-naming): trigger immediately on first message send
Moved tab naming logic from onSessionId callback to useInputProcessing hook. Tab naming now starts immediately when the user sends their first message, running in parallel with the actual agent request instead of waiting for the agent to respond with a session ID. Changes: - Added automaticTabNamingEnabled prop to UseInputProcessingDeps - Tab naming triggered in processInput for new AI sessions with text - Removed ~140 lines of tab naming code from onSessionId handler - Removed unused automaticTabNamingEnabledRef
This commit is contained in:
@@ -2624,60 +2624,6 @@ function MaestroConsoleInner() {
|
||||
const actualSessionId = parsed.actualSessionId;
|
||||
const tabId = parsed.tabId ?? undefined;
|
||||
|
||||
// First, check if we need to trigger automatic tab naming BEFORE updating state
|
||||
// We need the current session state to determine this
|
||||
const currentSessions = sessionsRef.current;
|
||||
const currentSession = currentSessions.find((s) => s.id === actualSessionId);
|
||||
|
||||
// Capture info for tab naming (must be done before state update modifies awaitingSessionId)
|
||||
interface TabNamingInfo {
|
||||
tabId: string;
|
||||
userMessage: string;
|
||||
agentType: string;
|
||||
cwd: string;
|
||||
sessionSshRemoteConfig?: {
|
||||
enabled: boolean;
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
};
|
||||
}
|
||||
let tabNamingInfo: TabNamingInfo | null = null;
|
||||
|
||||
if (currentSession) {
|
||||
// Find the target tab (same logic as in setSessions below)
|
||||
let targetTab = tabId
|
||||
? currentSession.aiTabs?.find((tab) => tab.id === tabId)
|
||||
: undefined;
|
||||
|
||||
if (!targetTab) {
|
||||
const awaitingTab = currentSession.aiTabs?.find(
|
||||
(tab) => tab.awaitingSessionId && !tab.agentSessionId
|
||||
);
|
||||
targetTab = awaitingTab || getActiveTab(currentSession);
|
||||
}
|
||||
|
||||
// Check if we should trigger automatic tab naming:
|
||||
// 1. Tab was awaiting session ID (this is a new session, not a resume)
|
||||
// 2. Tab doesn't already have a custom name
|
||||
// 3. Tab has at least one user message in logs
|
||||
if (targetTab?.awaitingSessionId && !targetTab.name) {
|
||||
const userMessages = targetTab.logs.filter((log) => log.source === 'user');
|
||||
if (userMessages.length > 0) {
|
||||
const firstUserMessage = userMessages[0];
|
||||
// Only use text messages for naming (skip image-only messages)
|
||||
if (firstUserMessage.text?.trim()) {
|
||||
tabNamingInfo = {
|
||||
tabId: targetTab.id,
|
||||
userMessage: firstUserMessage.text,
|
||||
agentType: currentSession.toolType,
|
||||
cwd: currentSession.cwd,
|
||||
sessionSshRemoteConfig: currentSession.sessionSshRemoteConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store Claude session ID in session state
|
||||
// Note: slash commands are now received via onSlashCommands from Claude Code's init message
|
||||
setSessions((prev) => {
|
||||
@@ -2741,94 +2687,6 @@ function MaestroConsoleInner() {
|
||||
return { ...s, aiTabs: updatedAiTabs, agentSessionId }; // Also keep session-level for backwards compatibility
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger automatic tab naming if conditions are met
|
||||
// This runs after the state update so it happens in parallel with the main AI processing
|
||||
// Use ref to access the latest setting value (useEffect has [] deps so we'd get stale value otherwise)
|
||||
if (tabNamingInfo && automaticTabNamingEnabledRef.current) {
|
||||
|
||||
// Set isGeneratingName to show spinner in tab
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== actualSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === tabNamingInfo!.tabId ? { ...t, isGeneratingName: true } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Call the tab naming API (async, don't await)
|
||||
window.maestro.tabNaming
|
||||
.generateTabName({
|
||||
userMessage: tabNamingInfo.userMessage,
|
||||
agentType: tabNamingInfo.agentType,
|
||||
cwd: tabNamingInfo.cwd,
|
||||
sessionSshRemoteConfig: tabNamingInfo.sessionSshRemoteConfig,
|
||||
})
|
||||
.then((generatedName) => {
|
||||
// Clear the generating indicator
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== actualSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === tabNamingInfo!.tabId ? { ...t, isGeneratingName: false } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (!generatedName) {
|
||||
console.log('[onSessionId] Tab naming returned null (timeout or error)');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[onSessionId] Tab naming generated:', generatedName);
|
||||
|
||||
// Update the tab name only if it's still null (user hasn't renamed it)
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== actualSessionId) return s;
|
||||
|
||||
const tab = s.aiTabs.find((t) => t.id === tabNamingInfo!.tabId);
|
||||
// Only update if tab exists and name is still null (UUID display)
|
||||
if (!tab || tab.name !== null) {
|
||||
console.log('[onSessionId] Skipping tab name update (already named)', {
|
||||
tabExists: !!tab,
|
||||
currentName: tab?.name,
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === tabNamingInfo!.tabId ? { ...t, name: generatedName } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[onSessionId] Tab naming failed:', error);
|
||||
// Clear the generating indicator on error
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== actualSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === tabNamingInfo!.tabId ? { ...t, isGeneratingName: false } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3582,14 +3440,12 @@ function MaestroConsoleInner() {
|
||||
const customAICommandsRef = useRef(customAICommands);
|
||||
const speckitCommandsRef = useRef(speckitCommands);
|
||||
const openspecCommandsRef = useRef(openspecCommands);
|
||||
const automaticTabNamingEnabledRef = useRef(automaticTabNamingEnabled);
|
||||
const fileTabAutoRefreshEnabledRef = useRef(fileTabAutoRefreshEnabled);
|
||||
addToastRef.current = addToast;
|
||||
updateGlobalStatsRef.current = updateGlobalStats;
|
||||
customAICommandsRef.current = customAICommands;
|
||||
speckitCommandsRef.current = speckitCommands;
|
||||
openspecCommandsRef.current = openspecCommands;
|
||||
automaticTabNamingEnabledRef.current = automaticTabNamingEnabled;
|
||||
fileTabAutoRefreshEnabledRef.current = fileTabAutoRefreshEnabled;
|
||||
|
||||
// Note: spawnBackgroundSynopsisRef and spawnAgentWithPromptRef are now provided by useAgentExecution hook
|
||||
@@ -7730,6 +7586,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
onWizardSendMessage: sendWizardMessageWithThinking,
|
||||
isWizardActive: isWizardActiveForCurrentTab,
|
||||
onSkillsCommand: handleSkillsCommand,
|
||||
automaticTabNamingEnabled,
|
||||
});
|
||||
|
||||
// Auto-send context when a tab with autoSendOnActivate becomes active
|
||||
|
||||
@@ -70,6 +70,8 @@ export interface UseInputProcessingDeps {
|
||||
isWizardActive?: boolean;
|
||||
/** Handler for the /skills built-in command (lists Claude Code skills) */
|
||||
onSkillsCommand?: () => Promise<void>;
|
||||
/** Whether automatic tab naming is enabled */
|
||||
automaticTabNamingEnabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +127,7 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
onWizardSendMessage,
|
||||
isWizardActive,
|
||||
onSkillsCommand,
|
||||
automaticTabNamingEnabled,
|
||||
} = deps;
|
||||
|
||||
// Ref for the processInput function so external code can access the latest version
|
||||
@@ -642,6 +645,84 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
})
|
||||
);
|
||||
|
||||
// Trigger automatic tab naming for new AI sessions immediately after sending the first message
|
||||
// This runs in parallel with the agent request (no need to wait for session ID)
|
||||
const activeTabForNaming = getActiveTab(activeSession);
|
||||
const isNewAiSession =
|
||||
currentMode === 'ai' && activeTabForNaming && !activeTabForNaming.agentSessionId;
|
||||
const hasTextMessage = effectiveInputValue.trim().length > 0;
|
||||
const hasNoCustomName = !activeTabForNaming?.name;
|
||||
|
||||
if (automaticTabNamingEnabled && isNewAiSession && hasTextMessage && hasNoCustomName) {
|
||||
// Set isGeneratingName to show spinner in tab
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== activeSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === activeTabForNaming.id ? { ...t, isGeneratingName: true } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Call the tab naming API (async, fire and forget)
|
||||
window.maestro.tabNaming
|
||||
.generateTabName({
|
||||
userMessage: effectiveInputValue,
|
||||
agentType: activeSession.toolType,
|
||||
cwd: activeSession.cwd,
|
||||
sessionSshRemoteConfig: activeSession.sessionSshRemoteConfig,
|
||||
})
|
||||
.then((generatedName) => {
|
||||
// Clear the generating indicator
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== activeSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === activeTabForNaming.id ? { ...t, isGeneratingName: false } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (!generatedName) return;
|
||||
|
||||
// Update the tab name only if it's still null (user hasn't manually renamed it)
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== activeSessionId) return s;
|
||||
const tab = s.aiTabs.find((t) => t.id === activeTabForNaming.id);
|
||||
if (!tab || tab.name !== null) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === activeTabForNaming.id ? { ...t, name: generatedName } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[processInput] Tab naming failed:', error);
|
||||
// Clear the generating indicator on error
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== activeSessionId) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map((t) =>
|
||||
t.id === activeTabForNaming.id ? { ...t, isGeneratingName: false } : t
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// If directory changed, check if new directory is a Git repository
|
||||
// For remote sessions, check remoteCwd; for local sessions, check shellCwd
|
||||
if (cwdChanged || remoteCwdChanged) {
|
||||
|
||||
Reference in New Issue
Block a user