From b00ebf73952e1df4306eb71028f0692ac67331da Mon Sep 17 00:00:00 2001 From: Quay Robot Date: Thu, 14 May 2026 17:43:32 +0000 Subject: [PATCH] feat(sessions): improve display name reliability and add creator to RECENTS tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements to session UX: 1. Display name generation now falls back to extracting the first sentence from the initial prompt when the Claude Haiku API call fails (API key misconfigured, timeout, network issues). Previously, failures were silent and left sessions showing as "session-UUID". 2. The RECENTS panel sidebar tooltip now shows the session creator name, matching the existing behavior on the /sessions page. Also improves MCP tool descriptions for display_name/name parameters to encourage agents to always provide meaningful session titles. ## Summary - Add `generateFallbackDisplayName()` in backend display_name.go - Fall back to prompt-based name when AI client init or API call fails - Add creator name (User icon + userContext.displayName) to sidebar tooltip - Update MCP tool descriptions in backend_tools.py and ambient-mcp/server.go ## Test plan - [ ] Create a session with an initial prompt — verify display name appears - [ ] Simulate API key failure — verify fallback name from prompt appears - [ ] Hover over a session in the RECENTS sidebar — verify creator name shows - [ ] Compare RECENTS tooltip with /sessions page tooltip — both show creator - [ ] Verify backend builds: `cd components/backend && go build ./handlers/...` - [ ] Verify frontend types: `cd components/frontend && tsc --noEmit` Co-Authored-By: Claude Opus 4.6 --- components/ambient-mcp/server.go | 2 +- components/backend/handlers/display_name.go | 57 ++++++++++++++----- .../components/sessions-sidebar.tsx | 4 ++ .../bridges/claude/backend_tools.py | 2 +- 4 files changed, 49 insertions(+), 16 deletions(-) mode change 100644 => 100755 components/ambient-mcp/server.go mode change 100644 => 100755 components/runners/ambient-runner/ambient_runner/bridges/claude/backend_tools.py diff --git a/components/ambient-mcp/server.go b/components/ambient-mcp/server.go old mode 100644 new mode 100755 index a93205cfe..31e9bdeb7 --- a/components/ambient-mcp/server.go +++ b/components/ambient-mcp/server.go @@ -62,7 +62,7 @@ func registerSessionTools(s *server.MCPServer, c *client.Client, transport strin mcp.WithString("agent_id", mcp.Description("Agent ID to execute the session.")), mcp.WithString("model", mcp.Description("LLM model override (e.g. 'claude-sonnet-4-6').")), mcp.WithString("parent_session_id", mcp.Description("Calling session ID for agent-to-agent delegation.")), - mcp.WithString("name", mcp.Description("Human-readable session name.")), + mcp.WithString("name", mcp.Description("Human-readable session name (max 50 chars). Provide a concise, descriptive title, e.g. 'Debug auth middleware'. Auto-generated from prompt if omitted.")), ), tools.CreateSession(c), ) diff --git a/components/backend/handlers/display_name.go b/components/backend/handlers/display_name.go index 90fd11133..bcfd24ba6 100755 --- a/components/backend/handlers/display_name.go +++ b/components/backend/handlers/display_name.go @@ -55,6 +55,29 @@ func sanitizeDisplayName(name string) string { return name } +// generateFallbackDisplayName creates a display name from the user's message +// when AI generation is unavailable. Extracts the first sentence or truncates. +func generateFallbackDisplayName(userMessage string) string { + msg := strings.TrimSpace(userMessage) + if msg == "" { + return "" + } + + // Find first sentence boundary + for _, sep := range []string{". ", "! ", "? ", "\n"} { + if idx := strings.Index(msg, sep); idx > 0 && idx <= maxDisplayNameLength { + msg = msg[:idx] + break + } + } + + runes := []rune(msg) + if len(runes) > maxDisplayNameLength { + msg = string(runes[:maxDisplayNameLength-3]) + "..." + } + return sanitizeDisplayName(msg) +} + // ValidateDisplayName validates a display name for the HTTP handler // Returns an error message if invalid, empty string if valid func ValidateDisplayName(name string) string { @@ -99,26 +122,32 @@ func generateAndUpdateDisplayName(projectName, sessionName, userMessage string, defer cancel() // Get Anthropic client (Vertex or API key) + var displayName string client, isVertex, err := getAnthropicClient(ctx, projectName) if err != nil { - return fmt.Errorf("failed to get Anthropic client: %w", err) - } - - // Build prompt with context - prompt := buildDisplayNamePrompt(userMessage, sessionCtx) - - // Call Claude Haiku with appropriate model name - modelName := haiku3Model - if isVertex { - modelName = haiku3ModelVertex - } - displayName, err := callClaudeForDisplayName(ctx, client, prompt, modelName) - if err != nil { - return fmt.Errorf("failed to call Claude: %w", err) + log.Printf("DisplayNameGen: Client init failed for %s/%s, using fallback: %v", projectName, sessionName, err) + displayName = generateFallbackDisplayName(userMessage) + } else { + // Build prompt with context + prompt := buildDisplayNamePrompt(userMessage, sessionCtx) + + // Call Claude Haiku with appropriate model name + modelName := haiku3Model + if isVertex { + modelName = haiku3ModelVertex + } + displayName, err = callClaudeForDisplayName(ctx, client, prompt, modelName) + if err != nil { + log.Printf("DisplayNameGen: AI generation failed for %s/%s, using fallback: %v", projectName, sessionName, err) + displayName = generateFallbackDisplayName(userMessage) + } } // Sanitize and validate display name displayName = sanitizeDisplayName(displayName) + if displayName == "" { + return fmt.Errorf("both AI and fallback generation produced empty display name") + } // Truncate if too long (using runes for proper Unicode handling) if utf8.RuneCountInString(displayName) > maxDisplayNameLength { diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/sessions-sidebar.tsx b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/sessions-sidebar.tsx index fe88d3c0e..5f9765a10 100755 --- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/sessions-sidebar.tsx +++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/sessions-sidebar.tsx @@ -496,6 +496,10 @@ export function SessionsSidebar({ {formatDistanceToNow(new Date(activityTime), { addSuffix: true })} )} +
+ + {session.spec.userContext?.displayName || session.spec.userContext?.userId || '—'} +
{session.spec.initialPrompt && (
diff --git a/components/runners/ambient-runner/ambient_runner/bridges/claude/backend_tools.py b/components/runners/ambient-runner/ambient_runner/bridges/claude/backend_tools.py old mode 100644 new mode 100755 index 0cc174ed0..287ac7bf3 --- a/components/runners/ambient-runner/ambient_runner/bridges/claude/backend_tools.py +++ b/components/runners/ambient-runner/ambient_runner/bridges/claude/backend_tools.py @@ -189,7 +189,7 @@ async def acp_get_session(args: dict) -> dict: }, "display_name": { "type": "string", - "description": "Human-readable display name", + "description": "Human-readable display name for the session (max 50 chars). Provide a concise, descriptive title based on the task, e.g. 'Debug auth middleware', 'Add user dashboard'. Auto-generated from initial_prompt if omitted.", }, "repos": { "type": "string",