generated from nhcarrigan/template
feat: hold tab at connecting until greeting response arrives
Track greeting-pending conversations so the tab stays yellow until Claude actually responds, rather than going green as soon as the process starts. Also fixes the disconnect handler missing a status update that left tabs stuck at yellow after the process died.
This commit is contained in:
+48
-4
@@ -29,6 +29,7 @@ interface StateChangePayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const connectedConversations = new Set<string>();
|
const connectedConversations = new Set<string>();
|
||||||
|
const greetingPendingConversations = new Set<string>();
|
||||||
let unlisteners: Array<() => void> = [];
|
let unlisteners: Array<() => void> = [];
|
||||||
let skipNextGreeting = false;
|
let skipNextGreeting = false;
|
||||||
|
|
||||||
@@ -55,17 +56,17 @@ function generateGreetingPrompt(): string {
|
|||||||
return `[System: A new session has started. It's currently ${timeOfDay}. Please greet the user warmly and briefly. Keep it short - just 1-2 sentences.]`;
|
return `[System: A new session has started. It's currently ${timeOfDay}. Please greet the user warmly and briefly. Keep it short - just 1-2 sentences.]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendGreeting(conversationId: string) {
|
async function sendGreeting(conversationId: string): Promise<boolean> {
|
||||||
// Check if we should skip this greeting
|
// Check if we should skip this greeting
|
||||||
if (skipNextGreeting) {
|
if (skipNextGreeting) {
|
||||||
skipNextGreeting = false; // Reset the flag
|
skipNextGreeting = false; // Reset the flag
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = configStore.getConfig();
|
const config = configStore.getConfig();
|
||||||
|
|
||||||
if (!config.greeting_enabled) {
|
if (!config.greeting_enabled) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const greetingPrompt = config.greeting_custom_prompt?.trim() || generateGreetingPrompt();
|
const greetingPrompt = config.greeting_custom_prompt?.trim() || generateGreetingPrompt();
|
||||||
@@ -81,10 +82,12 @@ async function sendGreeting(conversationId: string) {
|
|||||||
conversationId,
|
conversationId,
|
||||||
message: greetingPrompt,
|
message: greetingPrompt,
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to send greeting:", error);
|
console.error("Failed to send greeting:", error);
|
||||||
claudeStore.addLineToConversation(conversationId, "error", `Failed to send greeting: ${error}`);
|
claudeStore.addLineToConversation(conversationId, "error", `Failed to send greeting: ${error}`);
|
||||||
characterState.setTemporaryState("error", 3000);
|
characterState.setTemporaryState("error", 3000);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +121,7 @@ interface WorkingDirectoryPayload {
|
|||||||
|
|
||||||
export async function cleanupConversationTracking(conversationId: string) {
|
export async function cleanupConversationTracking(conversationId: string) {
|
||||||
connectedConversations.delete(conversationId);
|
connectedConversations.delete(conversationId);
|
||||||
|
greetingPendingConversations.delete(conversationId);
|
||||||
|
|
||||||
// Clean up any temp files associated with this conversation
|
// Clean up any temp files associated with this conversation
|
||||||
try {
|
try {
|
||||||
@@ -173,7 +177,24 @@ export async function initializeTauriListeners() {
|
|||||||
if (!connectedConversations.has(targetConversationId)) {
|
if (!connectedConversations.has(targetConversationId)) {
|
||||||
connectedConversations.add(targetConversationId);
|
connectedConversations.add(targetConversationId);
|
||||||
resetSessionStats(); // Reset session stats on new connection
|
resetSessionStats(); // Reset session stats on new connection
|
||||||
await sendGreeting(targetConversationId);
|
|
||||||
|
// Immediately hold the tab at yellow while we wait for the greeting response.
|
||||||
|
// This avoids a brief green flash before the greeting is even sent.
|
||||||
|
greetingPendingConversations.add(targetConversationId);
|
||||||
|
claudeStore.setConnectionStatusForConversation(
|
||||||
|
targetConversationId,
|
||||||
|
"connecting" as ConnectionStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
const greetingSent = await sendGreeting(targetConversationId);
|
||||||
|
if (!greetingSent) {
|
||||||
|
// Greeting was disabled or failed — flip straight to connected.
|
||||||
|
greetingPendingConversations.delete(targetConversationId);
|
||||||
|
claudeStore.setConnectionStatusForConversation(
|
||||||
|
targetConversationId,
|
||||||
|
"connected" as ConnectionStatus
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (status === "disconnected") {
|
} else if (status === "disconnected") {
|
||||||
@@ -191,6 +212,7 @@ export async function initializeTauriListeners() {
|
|||||||
// Only remove from connected set if we're not about to reconnect
|
// Only remove from connected set if we're not about to reconnect
|
||||||
if (!skipNextGreeting && targetConversationId) {
|
if (!skipNextGreeting && targetConversationId) {
|
||||||
connectedConversations.delete(targetConversationId);
|
connectedConversations.delete(targetConversationId);
|
||||||
|
greetingPendingConversations.delete(targetConversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't add system message if we're about to reconnect
|
// Don't add system message if we're about to reconnect
|
||||||
@@ -205,6 +227,14 @@ export async function initializeTauriListeners() {
|
|||||||
todos.clear();
|
todos.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the tab's connection status on real disconnects
|
||||||
|
if (!skipNextGreeting && targetConversationId) {
|
||||||
|
claudeStore.setConnectionStatusForConversation(
|
||||||
|
targetConversationId,
|
||||||
|
"disconnected" as ConnectionStatus
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update character state for this conversation
|
// Update character state for this conversation
|
||||||
if (targetConversationId) {
|
if (targetConversationId) {
|
||||||
claudeStore.setCharacterStateForConversation(targetConversationId, "idle");
|
claudeStore.setCharacterStateForConversation(targetConversationId, "idle");
|
||||||
@@ -214,6 +244,7 @@ export async function initializeTauriListeners() {
|
|||||||
|
|
||||||
if (targetConversationId) {
|
if (targetConversationId) {
|
||||||
connectedConversations.delete(targetConversationId);
|
connectedConversations.delete(targetConversationId);
|
||||||
|
greetingPendingConversations.delete(targetConversationId);
|
||||||
claudeStore.addLineToConversation(targetConversationId, "error", "Connection error");
|
claudeStore.addLineToConversation(targetConversationId, "error", "Connection error");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +306,19 @@ export async function initializeTauriListeners() {
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
// Flip to connected when first assistant message arrives after greeting
|
||||||
|
if (
|
||||||
|
conversation_id &&
|
||||||
|
line_type === "assistant" &&
|
||||||
|
greetingPendingConversations.has(conversation_id)
|
||||||
|
) {
|
||||||
|
greetingPendingConversations.delete(conversation_id);
|
||||||
|
claudeStore.setConnectionStatusForConversation(
|
||||||
|
conversation_id,
|
||||||
|
"connected" as ConnectionStatus
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Always store the output to the correct conversation
|
// Always store the output to the correct conversation
|
||||||
if (conversation_id) {
|
if (conversation_id) {
|
||||||
claudeStore.addLineToConversation(
|
claudeStore.addLineToConversation(
|
||||||
|
|||||||
Reference in New Issue
Block a user