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:
Pedram Amini
2026-02-02 23:47:44 -06:00
parent d15e023ea9
commit 0f701e4ebd
2 changed files with 82 additions and 144 deletions

View File

@@ -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

View File

@@ -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) {