diff --git a/.github/workflows/claude-live-test.yml b/.github/workflows/claude-live-test.yml
index 7b6f3cad7..cb30491ef 100644
--- a/.github/workflows/claude-live-test.yml
+++ b/.github/workflows/claude-live-test.yml
@@ -216,6 +216,8 @@ jobs:
- name: Run Claude with Playwright MCP
uses: anthropics/claude-code-action@v1
+ continue-on-error: true
+ id: claude_test
env:
TEST_TOKEN: ${{ steps.token.outputs.token }}
DEBUG: "*"
diff --git a/.github/workflows/components-build-deploy.yml b/.github/workflows/components-build-deploy.yml
index 04baca254..3681bc3b1 100644
--- a/.github/workflows/components-build-deploy.yml
+++ b/.github/workflows/components-build-deploy.yml
@@ -265,7 +265,8 @@ jobs:
run: |
oc set env deployment/frontend -n ambient-code -c frontend \
GITHUB_APP_SLUG="ambient-code-stage" \
- VTEAM_VERSION="${{ github.sha }}"
+ VTEAM_VERSION="${{ github.sha }}" \
+ FEEDBACK_URL="https://forms.gle/7XiWrvo6No922DUz6"
- name: Update backend environment variables
if: needs.detect-changes.outputs.backend == 'true'
@@ -328,7 +329,8 @@ jobs:
run: |
oc set env deployment/frontend -n ambient-code -c frontend \
GITHUB_APP_SLUG="ambient-code-stage" \
- VTEAM_VERSION="${{ github.sha }}"
+ VTEAM_VERSION="${{ github.sha }}" \
+ FEEDBACK_URL="https://forms.gle/7XiWrvo6No922DUz6"
- name: Update backend environment variables
run: |
diff --git a/.github/workflows/prod-release-deploy.yaml b/.github/workflows/prod-release-deploy.yaml
index e2e3235d6..694a755f4 100644
--- a/.github/workflows/prod-release-deploy.yaml
+++ b/.github/workflows/prod-release-deploy.yaml
@@ -265,7 +265,8 @@ jobs:
run: |
oc set env deployment/frontend -n ambient-code -c frontend \
GITHUB_APP_SLUG="ambient-code" \
- VTEAM_VERSION="${{ needs.release.outputs.new_tag }}"
+ VTEAM_VERSION="${{ needs.release.outputs.new_tag }}" \
+ FEEDBACK_URL="https://forms.gle/7XiWrvo6No922DUz6"
- name: Update backend environment variables
run: |
diff --git a/components/backend/handlers/content.go b/components/backend/handlers/content.go
index e0cd4aad5..ea7cd6cfb 100644
--- a/components/backend/handlers/content.go
+++ b/components/backend/handlers/content.go
@@ -663,15 +663,20 @@ func ContentWorkflowMetadata(c *gin.Context) {
log.Printf("ContentWorkflowMetadata: agents directory not found or unreadable: %v", err)
}
+ configResponse := gin.H{
+ "name": ambientConfig.Name,
+ "description": ambientConfig.Description,
+ "systemPrompt": ambientConfig.SystemPrompt,
+ "artifactsDir": ambientConfig.ArtifactsDir,
+ }
+ if ambientConfig.Rubric != nil {
+ configResponse["rubric"] = ambientConfig.Rubric
+ }
+
c.JSON(http.StatusOK, gin.H{
"commands": commands,
"agents": agents,
- "config": gin.H{
- "name": ambientConfig.Name,
- "description": ambientConfig.Description,
- "systemPrompt": ambientConfig.SystemPrompt,
- "artifactsDir": ambientConfig.ArtifactsDir,
- },
+ "config": configResponse,
})
}
@@ -713,12 +718,21 @@ func parseFrontmatter(filePath string) map[string]string {
return result
}
+// RubricConfig represents the rubric evaluation configuration in ambient.json.
+// Schema is a JSON Schema object that defines the tool's input_schema for
+// additional metadata fields beyond final_score and reasoning.
+type RubricConfig struct {
+ ActivationPrompt string `json:"activationPrompt,omitempty"`
+ Schema map[string]interface{} `json:"schema,omitempty"`
+}
+
// AmbientConfig represents the ambient.json configuration
type AmbientConfig struct {
- Name string `json:"name"`
- Description string `json:"description"`
- SystemPrompt string `json:"systemPrompt"`
- ArtifactsDir string `json:"artifactsDir"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ SystemPrompt string `json:"systemPrompt"`
+ ArtifactsDir string `json:"artifactsDir"`
+ Rubric *RubricConfig `json:"rubric,omitempty"`
}
// parseAmbientConfig reads and parses ambient.json from workflow directory
diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/accordions/mcp-integrations-accordion.tsx b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/accordions/mcp-integrations-accordion.tsx
index d6da85ae8..604cf2b5a 100644
--- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/accordions/mcp-integrations-accordion.tsx
+++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/accordions/mcp-integrations-accordion.tsx
@@ -1,9 +1,8 @@
'use client'
import { useState, useEffect } from 'react'
-import type { ReactNode } from 'react'
import Link from 'next/link'
-import { Plug, CheckCircle2, XCircle, AlertCircle, AlertTriangle } from 'lucide-react'
+import { Plug, Link2, CheckCircle2, XCircle, AlertCircle, AlertTriangle, Info, Check, X } from 'lucide-react'
import {
AccordionItem,
AccordionTrigger,
@@ -16,87 +15,89 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover'
import { Skeleton } from '@/components/ui/skeleton'
import { useMcpStatus } from '@/services/queries/use-mcp'
-import { useProjectIntegrationStatus } from '@/services/queries/use-projects'
import { useIntegrationsStatus } from '@/services/queries/use-integrations'
-import type { McpServer } from '@/services/api/sessions'
+import type { McpServer, McpTool } from '@/services/api/sessions'
-type McpIntegrationsAccordionProps = {
+// ─── MCP Servers Accordion ───────────────────────────────────────────────────
+
+type McpServersAccordionProps = {
projectName: string
sessionName: string
+ sessionPhase?: string
}
-export function McpIntegrationsAccordion({
+export function McpServersAccordion({
projectName,
sessionName,
-}: McpIntegrationsAccordionProps) {
+ sessionPhase,
+}: McpServersAccordionProps) {
const [placeholderTimedOut, setPlaceholderTimedOut] = useState(false)
- // Fetch real MCP status from runner
- const { data: mcpStatus, isPending: mcpPending } = useMcpStatus(projectName, sessionName)
+ // Only fetch MCP status once the session is actually running (runner pod ready)
+ const isRunning = sessionPhase === 'Running'
+ const { data: mcpStatus, isPending: mcpPending } = useMcpStatus(projectName, sessionName, isRunning)
const mcpServers = mcpStatus?.servers || []
- const { data: integrationStatus, isPending: integrationStatusPending } =
- useProjectIntegrationStatus(projectName)
- const githubConfigured = integrationStatus?.github ?? false
-
- const { data: integrationsStatus } = useIntegrationsStatus()
- const gitlabConfigured = integrationsStatus?.gitlab?.connected ?? false
-
- // Show skeleton cards until we have MCP servers or 2 min elapsed (backend returns empty when runner not ready)
const showPlaceholders =
- mcpPending || (mcpServers.length === 0 && !placeholderTimedOut)
+ !isRunning || mcpPending || (mcpServers.length === 0 && !placeholderTimedOut)
useEffect(() => {
if (mcpServers.length > 0) {
setPlaceholderTimedOut(false)
return
}
- if (!mcpStatus) return
- const t = setTimeout(() => setPlaceholderTimedOut(true), 15 * 1000) // 15 seconds
+ if (!isRunning || !mcpStatus) return
+ const t = setTimeout(() => setPlaceholderTimedOut(true), 15 * 1000)
return () => clearTimeout(t)
- }, [mcpStatus, mcpServers.length])
+ }, [mcpStatus, mcpServers.length, isRunning])
- // Collect all MCP servers
- const allServers = [...mcpServers]
-
- // Ensure core integrations always appear (even if not in API response)
- if (!showPlaceholders) {
- // Webfetch - always available
- const hasWebfetch = allServers.some((s) => s.name === 'webfetch')
- if (!hasWebfetch) {
- allServers.push({
- name: 'webfetch',
- displayName: 'Webfetch',
- status: 'disconnected',
- authenticated: undefined,
- authMessage: 'Fetches web content for the session.',
- } as McpServer)
- }
-
- // Google Workspace - show as not configured if missing
- const hasGoogleWorkspace = allServers.some((s) => s.name === 'google-workspace')
- if (!hasGoogleWorkspace) {
- allServers.push({
- name: 'google-workspace',
- displayName: 'Google Workspace',
- status: 'disconnected',
- authenticated: false,
- authMessage: undefined,
- } as McpServer)
+ const getStatusIcon = (server: McpServer) => {
+ switch (server.status) {
+ case 'configured':
+ case 'connected':
+ return
not configured
-{tool.name}
+ {annotations.length > 0 && (
+ + {server.displayName} — {toolCount} {toolCount === 1 ? 'tool' : 'tools'} +
+- {githubConfigured ? ( - 'MCP access to GitHub repositories.' - ) : ( +
+ No MCP servers available for this session. +
)} - +not configured
+Not configured
- {gitlabConfigured ? ( - 'MCP access to GitLab repositories.' + {integration.configured ? ( + integration.configuredMessage ) : ( <> - Session started without GitLab MCP. Configure{' '} + Not connected.{' '} - Integrations + Set up {' '} - and start a new session. + to enable {integration.name} access. > )}
not configured
-- {getDescription(server)} -
- )} -{server.authMessage}
-- MCP access to Jira issues and projects. + Authenticated. Git push and repository access enabled.
- Configure{" "} - - Integrations + Not connected.{" "} + + Set up {" "} - to access Jira MCP in this session. + to enable repository access.
- MCP access to GitHub repositories. + Authenticated. Git push and repository access enabled.
- Configure{" "} + Not connected.{" "} - Integrations + Set up {" "} - to access GitHub MCP in this session. + to enable repository access.
- MCP access to GitLab repositories. + Authenticated. Drive, Calendar, and Gmail access enabled.
- Configure{" "} + Not connected.{" "} - Integrations + Set up {" "} - to access GitLab MCP in this session. + to enable Drive, Calendar, and Gmail access.
+ Authenticated. Issue and project access enabled. +
+- Configure{" "} - - Integrations - {" "} - to access Google Workspace MCP in this session. -
+ ) : ( ++ Not connected.{" "} + + Set up + {" "} + to enable issue and project access. +
+