diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3159a30b..3033d020 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -219,6 +219,7 @@ async function probeNewChatCapabilities( type CreateMode = QuickCreateKind | "package"; type CreateView = "menu" | CreateMode | null; +type CustomCreateMode = "custom" | "yaml_import"; // Persist the last view so a page refresh restores where the user was. const LS = { app: "veadk.appName", view: "veadk.view", session: "veadk.sessionId" } as const; @@ -307,9 +308,15 @@ import { initStudioTelemetry, } from "./adk/telemetry"; import { + trackAgentConnectFailed, + trackAgentConnectSucceeded, + trackAgentMessageFailed, + trackAgentMessageSucceeded, trackSandboxCreateFailed, trackSandboxCreateSucceeded, trackStudioLoaded, + type AgentConnectSource, + type AgentMessageSource, } from "./adk/telemetryEvents"; import type { A2uiAction, A2uiComponent } from "./a2ui/types"; import { buildSurfaces } from "./a2ui/Surface"; @@ -1138,6 +1145,8 @@ export default function App() { const [addMenu, setAddMenu] = useState(false); // A draft imported from YAML, used to pre-fill the custom wizard once. const [importedDraft, setImportedDraft] = useState(null); + const [customCreateMode, setCustomCreateMode] = + useState("custom"); const [savedAgentDrafts, setSavedAgentDrafts] = useState([]); const savedAgentDraftsRef = useRef([]); const pendingWorkspaceDraftRef = useRef(null); @@ -2349,32 +2358,59 @@ export default function App() { } } - async function openSandboxAgent(session: SandboxSessionInfo) { + async function openSandboxAgent( + session: SandboxSessionInfo, + source: AgentConnectSource = "my_agents", + ) { setError(""); - if (session.toolName === "codex") { - const connected = await sandboxClient.connectSession(session.id); - viewSidRef.current = ""; - setSessionId(""); - setPendingTurns([]); - setInput(""); - setInvocation(emptyInvocation()); - releaseAllSandboxPreviews(); - setSandboxTurns([]); - setSandboxSession(connected); + const startedAt = Date.now(); + try { + if (session.toolName === "codex") { + const connected = await sandboxClient.connectSession(session.id); + trackAgentConnectSucceeded({ + kind: session.toolName, + source, + durationMs: Date.now() - startedAt, + sandboxStatus: connected.status, + }); + viewSidRef.current = ""; + setSessionId(""); + setPendingTurns([]); + setInput(""); + setInvocation(emptyInvocation()); + releaseAllSandboxPreviews(); + setSandboxTurns([]); + setSandboxSession(connected); + setSandboxAgentDetailTarget(null); + setSandboxAgentWorkspace(null); + setMyAgents(false); + setManageAgents(false); + return; + } + const workspace = await sandboxClient.openAgentSession( + session.toolName, + session.id, + ); + trackAgentConnectSucceeded({ + kind: session.toolName, + source, + durationMs: Date.now() - startedAt, + sandboxStatus: workspace.session.status, + }); + setSandboxAgentWorkspace(workspace); setSandboxAgentDetailTarget(null); - setSandboxAgentWorkspace(null); setMyAgents(false); setManageAgents(false); - return; + } catch (cause) { + trackAgentConnectFailed({ + kind: session.toolName, + source, + durationMs: Date.now() - startedAt, + error: cause, + }); + setError(cause instanceof Error ? cause.message : String(cause)); + throw cause; } - const workspace = await sandboxClient.openAgentSession( - session.toolName, - session.id, - ); - setSandboxAgentWorkspace(workspace); - setSandboxAgentDetailTarget(null); - setMyAgents(false); - setManageAgents(false); } function openSandboxAgentDetails(session: SandboxSessionInfo) { @@ -2696,6 +2732,7 @@ export default function App() { setError(""); setSandboxApproval(null); setSandboxApprovalError(""); + const messageStartedAt = Date.now(); const controller = new AbortController(); sandboxMessageAbortRef.current?.abort(); sandboxMessageAbortRef.current = controller; @@ -2817,6 +2854,12 @@ export default function App() { }, ); if (sandboxMessageAbortRef.current !== controller) return; + trackAgentMessageSucceeded({ + kind: activeSession.toolName, + source: "composer", + sessionState: "existing", + durationMs: Date.now() - messageStartedAt, + }); setSandboxTurns((current) => { const next = current.slice(); const assistantIndex = next.findIndex( @@ -2845,6 +2888,14 @@ export default function App() { if (sandboxMessageAbortRef.current !== controller) { return; } + trackAgentMessageFailed({ + kind: activeSession.toolName, + source: "composer", + sessionState: "existing", + durationMs: Date.now() - messageStartedAt, + phase: "sandbox_send", + error: messageError, + }); setSandboxTurns((current) => current.filter( (turn) => @@ -3232,6 +3283,7 @@ export default function App() { text: string, atts: Attachment[] = [], selectedInvocation: FrontendInvocation = emptyInvocation(), + messageSource: AgentMessageSource = "composer", ) { // `busy` here = the CURRENT session is already streaming (can't double-send // to it). Other sessions can stream concurrently. @@ -3243,6 +3295,10 @@ export default function App() { !userId ) return; setError(""); + const messageStartedAt = Date.now(); + const createsSession = !sessionId; + const sessionState = createsSession ? "new" : "existing"; + const trackRuntimeMessage = Boolean(currentRuntime); const userBlocks: Turn["blocks"] = []; if (selectedInvocation.skills.length > 0 || selectedInvocation.targetAgent) { @@ -3265,7 +3321,6 @@ export default function App() { { role: "user", blocks: userBlocks, meta: { ts: Date.now() / 1000 } }, { role: "assistant", blocks: [] }, ]; - const createsSession = !sessionId; if (createsSession) { setPendingTurns(optimisticTurns); setInitializingSession(true); @@ -3282,6 +3337,16 @@ export default function App() { setInput(text); setInvocation(selectedInvocation); } + if (trackRuntimeMessage) { + trackAgentMessageFailed({ + kind: "runtime", + source: messageSource, + sessionState, + durationMs: Date.now() - messageStartedAt, + phase: "create_session", + error: e, + }); + } setError(String(e)); return; } @@ -3317,6 +3382,16 @@ export default function App() { setInput(text); setInvocation(selectedInvocation); } + if (trackRuntimeMessage) { + trackAgentMessageFailed({ + kind: "runtime", + source: messageSource, + sessionState, + durationMs: Date.now() - messageStartedAt, + phase: "mount_task_capabilities", + error: e, + }); + } setError(`任务能力挂载失败:${String(e)}`); return; } @@ -3351,6 +3426,7 @@ export default function App() { let eventId = ""; let invocationId = ""; let streamFailed = false; + let streamError: unknown = null; for await (const event of runSSE({ appName, userId, @@ -3365,6 +3441,7 @@ export default function App() { const errMsg = event.error ?? event.errorMessage ?? event.error_message; if (typeof errMsg === "string" && errMsg) { streamFailed = true; + streamError = errMsg; if (viewSidRef.current === sid) setError(errMsg); break; } @@ -3407,6 +3484,25 @@ export default function App() { }); } void refreshSessions(appName); + if (!ctrl.signal.aborted && trackRuntimeMessage) { + if (streamFailed) { + trackAgentMessageFailed({ + kind: "runtime", + source: messageSource, + sessionState, + durationMs: Date.now() - messageStartedAt, + phase: "run_sse", + error: streamError ?? "run_sse failed", + }); + } else { + trackAgentMessageSucceeded({ + kind: "runtime", + source: messageSource, + sessionState, + durationMs: Date.now() - messageStartedAt, + }); + } + } if (!ctrl.signal.aborted && !streamFailed && eventId) { automaticEvaluationStatusRefreshRef.current(); } @@ -3418,6 +3514,16 @@ export default function App() { !ctrl.signal.aborted && viewSidRef.current === sid ) { + if (trackRuntimeMessage) { + trackAgentMessageFailed({ + kind: "runtime", + source: messageSource, + sessionState, + durationMs: Date.now() - messageStartedAt, + phase: "run_sse", + error: e, + }); + } setError(String(e)); } } finally { @@ -3432,7 +3538,12 @@ export default function App() { function onAction(action: A2uiAction | undefined, node: A2uiComponent) { const name = action?.event?.name ?? node.id; const context = action?.event?.context ?? {}; - send(`[ui-action] ${name}: ${JSON.stringify(context)}`); + void send( + `[ui-action] ${name}: ${JSON.stringify(context)}`, + [], + emptyInvocation(), + "a2ui_action", + ); } /** Complete an MCP/tool OAuth request: open the authorize URL, capture the @@ -3880,8 +3991,12 @@ export default function App() { setError(""); }; - const connectMyAgent = async (agent: MyAgentCardData, rethrow = false) => { - if (!agent.runtime) return; + const connectRuntimeForUser = async ( + agent: MyAgentCardData, + source: AgentConnectSource, + ): Promise => { + if (!agent.runtime) throw new Error("缺少 Runtime 信息,无法连接智能体。"); + const startedAt = Date.now(); try { const agentId = await connectRuntime( agent.runtime.runtimeId, @@ -3889,11 +4004,40 @@ export default function App() { agent.runtime.region, agent.runtime.currentVersion, ); + trackAgentConnectSucceeded({ + kind: "runtime", + source, + durationMs: Date.now() - startedAt, + runtimeRegion: agent.runtime.region, + runtimeIsMine: agent.isMine, + }); + return agentId; + } catch (error) { + trackAgentConnectFailed({ + kind: "runtime", + source, + durationMs: Date.now() - startedAt, + error, + }); + throw error; + } + }; + + const connectMyAgent = async ( + agent: MyAgentCardData, + options: { rethrow?: boolean; source?: AgentConnectSource } = {}, + ) => { + if (!agent.runtime) return; + try { + const agentId = await connectRuntimeForUser( + agent, + options.source ?? "my_agents", + ); await refreshCurrentAgentAndStartNewChat(agentId); } catch (cause) { const message = cause instanceof Error ? cause.message : String(cause); setError(message); - if (rethrow) throw new Error(message); + if (options.rethrow) throw new Error(message); } }; @@ -3961,6 +4105,7 @@ export default function App() { setFeedbackCaseReturnAgentId(""); setFeedbackTargetEventId(""); if (agent.runtimeId && agent.id.startsWith("detail:")) { + const startedAt = Date.now(); try { const agentId = await connectRuntime( agent.runtimeId, @@ -3968,8 +4113,20 @@ export default function App() { agent.region ?? "cn-beijing", agent.currentVersion, ); + trackAgentConnectSucceeded({ + kind: "runtime", + source: "agent_workspace", + durationMs: Date.now() - startedAt, + runtimeRegion: agent.region, + }); await refreshCurrentAgentAndStartNewChat(agentId); } catch (cause) { + trackAgentConnectFailed({ + kind: "runtime", + source: "agent_workspace", + durationMs: Date.now() - startedAt, + error: cause, + }); setError(cause instanceof Error ? cause.message : String(cause)); } return; @@ -4298,24 +4455,29 @@ export default function App() { selectedRuntimeId={currentRuntime?.runtimeId} runtimeScope={access.capabilities.runtimeScope} onSelectRuntime={async (runtime) => { - await connectMyAgent({ - id: runtime.runtimeId, - name: runtime.name, - description: runtime.description?.trim() || "暂无描述", - createdAt: runtime.createdAt ?? "", - specificationLabel: "地域", - specification: - runtime.region === "cn-shanghai" ? "上海" : "北京", - isMine: runtime.isMine, - runtime: { - runtimeId: runtime.runtimeId, - region: runtime.region, - currentVersion: runtime.currentVersion, - canDelete: runtime.canDelete, + await connectMyAgent( + { + id: runtime.runtimeId, + name: runtime.name, + description: runtime.description?.trim() || "暂无描述", + createdAt: runtime.createdAt ?? "", + specificationLabel: "地域", + specification: + runtime.region === "cn-shanghai" ? "上海" : "北京", + isMine: runtime.isMine, + runtime: { + runtimeId: runtime.runtimeId, + region: runtime.region, + currentVersion: runtime.currentVersion, + canDelete: runtime.canDelete, + }, }, - }, true); + { rethrow: true, source: "new_chat_picker" }, + ); }} - onSelectSandboxSession={openSandboxAgent} + onSelectSandboxSession={(session) => + openSandboxAgent(session, "new_chat_picker") + } showModeSelector={false} temporaryEnabled={newChatCapabilitiesReady && newChatCapabilities.temporaryEnabled} skillCreateEnabled={newChatCapabilitiesReady && newChatCapabilities.skillCreateEnabled} @@ -4415,7 +4577,9 @@ export default function App() { openSandboxAgent(sandboxAgentDetailTarget)} + onOpen={() => + openSandboxAgent(sandboxAgentDetailTarget, "sandbox_detail") + } onDelete={() => deleteSandboxAgent(sandboxAgentDetailTarget)} /> ) : myAgents ? ( @@ -4423,10 +4587,14 @@ export default function App() { canCreate={canCreateAgents} runtimeScope={access.capabilities.runtimeScope} onCreateAgent={openAgentCreateFromMyAgents} - onUseAgent={connectMyAgent} + onUseAgent={(agent) => + connectMyAgent(agent, { source: "my_agents" }) + } onViewAgentDetails={openMyAgentDetails} onCreateSandboxAgent={openSandboxAgentCreate} - onUseSandboxAgent={openSandboxAgent} + onUseSandboxAgent={(session) => + openSandboxAgent(session, "my_agents") + } onViewSandboxAgentDetails={openSandboxAgentDetails} sandboxRefreshKey={sandboxAgentRefreshKey} connectedRuntimeId={connectedRuntimeId} @@ -4438,6 +4606,7 @@ export default function App() { onEditDraft={(item) => { setMyAgents(false); setImportedDraft(item.draft); + setCustomCreateMode("custom"); setEditingDraftId(item.id); editingDraftBaselineRef.current = item; setRuntimeUpdateTarget(item.deploymentTarget ?? null); @@ -4531,6 +4700,7 @@ export default function App() { }; setManageAgents(false); setImportedDraft(hydratedDraft); + setCustomCreateMode("custom"); const nextDraftId = `runtime-${capability.runtime.runtimeId}`; setEditingDraftId(nextDraftId); editingDraftBaselineRef.current = @@ -4550,6 +4720,7 @@ export default function App() { onEditDraft={(item) => { setManageAgents(false); setImportedDraft(item.draft); + setCustomCreateMode("custom"); setEditingDraftId(item.id); editingDraftBaselineRef.current = item; setRuntimeUpdateTarget(item.deploymentTarget ?? null); @@ -4648,6 +4819,7 @@ export default function App() { setRuntimeUpdateTarget(null); setFocusedDeploymentTaskId(""); setFocusedWorkspaceAgentId(""); + if (k === "custom") setCustomCreateMode("custom"); setEditingDraftId( k === "custom" ? `draft-${Date.now().toString(36)}` : "", ); @@ -4656,6 +4828,7 @@ export default function App() { }} onImport={(d) => { setImportedDraft(d); + setCustomCreateMode("yaml_import"); setRuntimeUpdateTarget(null); setFocusedDeploymentTaskId(""); setFocusedWorkspaceAgentId(""); @@ -4681,6 +4854,7 @@ export default function App() { onAgentAdded={onAgentAdded} features={features} onDeploymentTaskChange={updateDeploymentTask} + createMode={customCreateMode} deploymentTarget={runtimeUpdateTarget ?? undefined} initialDeployRegion={newRuntimeRegion} onDraftChange={(draft, dirty) => { diff --git a/frontend/src/adk/telemetry.ts b/frontend/src/adk/telemetry.ts index 3b08d2e8..48ad84f4 100644 --- a/frontend/src/adk/telemetry.ts +++ b/frontend/src/adk/telemetry.ts @@ -7,10 +7,12 @@ import type { export type StudioTelemetryEventName = | "studio_instance_loaded" | "studio_user_authenticated" - | "studio_agent_deploy_succeeded" - | "studio_agent_deploy_failed" - | "studio_sandbox_create_succeeded" - | "studio_sandbox_create_failed"; + | "studio_agent_deploy" + | "studio_sandbox_create" + | "studio_agent_debug" + | "studio_agent_connect" + | "studio_agent_message" + | "studio_agent_source_download"; export interface StudioTelemetryEventOptions { dedupeKey?: string; diff --git a/frontend/src/adk/telemetryClassifiers.ts b/frontend/src/adk/telemetryClassifiers.ts index ebb2b7b9..eccbefc7 100644 --- a/frontend/src/adk/telemetryClassifiers.ts +++ b/frontend/src/adk/telemetryClassifiers.ts @@ -6,6 +6,38 @@ export function sandboxCreateErrorKind(error: unknown): string { return "unknown"; } +export function agentDebugErrorKind(error: unknown): string { + if ((error as Error | undefined)?.name === "AbortError") return "abort"; + if (error instanceof Error && error.name && error.name !== "Error") { + return error.name; + } + return "unknown"; +} + +export function agentConnectErrorKind(error: unknown): string { + if ((error as Error | undefined)?.name === "AbortError") return "abort"; + if (error instanceof Error && error.name && error.name !== "Error") { + return error.name; + } + return "unknown"; +} + +export function agentMessageErrorKind(error: unknown): string { + if ((error as Error | undefined)?.name === "AbortError") return "abort"; + if (error instanceof Error && error.name && error.name !== "Error") { + return error.name; + } + return "unknown"; +} + +export function agentSourceDownloadErrorKind(error: unknown): string { + if ((error as Error | undefined)?.name === "AbortError") return "abort"; + if (error instanceof Error && error.name && error.name !== "Error") { + return error.name; + } + return "unknown"; +} + export function agentDeployErrorKind(error: unknown, phase: string): string { if (phase === "build") return "build_failed"; if ((error as Error | undefined)?.name === "RuntimeProbeError") { @@ -19,3 +51,13 @@ export function agentDeployErrorKind(error: unknown, phase: string): string { } return "unknown"; } + +export function telemetryErrorSummary(error: unknown): string { + const raw = error instanceof Error ? error.message : String(error); + return raw + .replace( + /\b((?:app[_-]?)?secret|token|api[_-]?key|password)\b\s*[:=]\s*["']?[^"',\s}]+/gi, + "$1=", + ) + .slice(0, 300); +} diff --git a/frontend/src/adk/telemetryEvents.ts b/frontend/src/adk/telemetryEvents.ts index 4b0fbe23..eb4a3071 100644 --- a/frontend/src/adk/telemetryEvents.ts +++ b/frontend/src/adk/telemetryEvents.ts @@ -1,16 +1,37 @@ import { + agentConnectErrorKind, + agentDebugErrorKind, agentDeployErrorKind, + agentMessageErrorKind, + agentSourceDownloadErrorKind, sandboxCreateErrorKind, + telemetryErrorSummary, } from "./telemetryClassifiers"; import { trackStudioEvent } from "./telemetry"; import type { SandboxAgentKind } from "./sandbox"; export type DeploymentTelemetrySource = - | "custom_create" - | "intelligent_create" + | "scratch" | "code_package" + | "feishu_automation" | "unknown"; +export type DeploymentCreateMode = + | "custom" + | "intelligent" + | "template" + | "workflow" + | "yaml_import" + | "code_package" + | "feishu_template" + | "unknown"; + +export interface DeploymentTelemetryOrigin { + source: DeploymentTelemetrySource; + createMode: DeploymentCreateMode; + aiAssisted: boolean; +} + export interface StudioLoadedTelemetry { agentsSource: "local" | "cloud"; } @@ -18,7 +39,7 @@ export interface StudioLoadedTelemetry { export type SandboxTelemetryKind = "codex" | SandboxAgentKind; export interface AgentDeployTelemetryBase { - source: DeploymentTelemetrySource; + telemetry: DeploymentTelemetryOrigin; action: "create" | "update"; region: string; networkType: string; @@ -47,9 +68,93 @@ export interface SandboxCreateFailedTelemetry extends SandboxCreateTelemetryBase error: unknown; } +export type AgentDebugVariantType = "baseline" | "comparison"; +export type AgentDebugFailedPhase = "create_test_run" | "create_test_session"; + +export interface AgentDebugTelemetryBase { + durationMs: number; + variantType?: AgentDebugVariantType; +} + +export interface AgentDebugFailedTelemetry extends AgentDebugTelemetryBase { + phase?: AgentDebugFailedPhase; + error: unknown; +} + +export type AgentConnectKind = + | "runtime" + | "local" + | SandboxTelemetryKind; + +export type AgentConnectSource = + | "new_chat_picker" + | "my_agents" + | "agent_workspace" + | "navbar_picker" + | "sandbox_detail"; + +export interface AgentConnectTelemetryBase { + kind: AgentConnectKind; + source: AgentConnectSource; + durationMs: number; +} + +export interface AgentConnectSucceededTelemetry extends AgentConnectTelemetryBase { + runtimeRegion?: string; + runtimeIsMine?: boolean; + sandboxStatus?: string; +} + +export interface AgentConnectFailedTelemetry extends AgentConnectTelemetryBase { + error: unknown; +} + +export type AgentMessageKind = + | "runtime" + | SandboxTelemetryKind; + +export type AgentMessageSource = "composer" | "a2ui_action"; +export type AgentMessageSessionState = "new" | "existing"; +export type AgentMessageFailedPhase = + | "create_session" + | "mount_task_capabilities" + | "run_sse" + | "sandbox_send"; + +export interface AgentMessageTelemetryBase { + kind: AgentMessageKind; + source: AgentMessageSource; + sessionState: AgentMessageSessionState; + durationMs: number; +} + +export interface AgentMessageFailedTelemetry extends AgentMessageTelemetryBase { + phase: AgentMessageFailedPhase; + error: unknown; +} + +export interface AgentSourceDownloadTelemetryBase { + telemetry: DeploymentTelemetryOrigin; + action: "create" | "update"; + fileCount: number; + durationMs: number; +} + +export interface AgentSourceDownloadSucceededTelemetry + extends AgentSourceDownloadTelemetryBase { + zipSizeBytes: number; +} + +export interface AgentSourceDownloadFailedTelemetry + extends AgentSourceDownloadTelemetryBase { + error: unknown; +} + function agentDeployCategories(args: AgentDeployTelemetryBase) { return { - deploy_source: args.source, + deploy_source: args.telemetry.source, + create_mode: args.telemetry.createMode, + ai_assisted: args.telemetry.aiAssisted, deploy_action: args.action, deploy_region: args.region, runtime_network_type: args.networkType, @@ -57,6 +162,18 @@ function agentDeployCategories(args: AgentDeployTelemetryBase) { }; } +function deploymentOriginCategories(args: { + telemetry: DeploymentTelemetryOrigin; + action: "create" | "update"; +}) { + return { + deploy_source: args.telemetry.source, + create_mode: args.telemetry.createMode, + ai_assisted: args.telemetry.aiAssisted, + deploy_action: args.action, + }; +} + export function trackStudioLoaded(args: StudioLoadedTelemetry): void { trackStudioEvent( "studio_instance_loaded", @@ -71,24 +188,28 @@ export function trackStudioLoaded(args: StudioLoadedTelemetry): void { export function trackAgentDeploySucceeded( args: AgentDeploySucceededTelemetry, ): void { - trackStudioEvent("studio_agent_deploy_succeeded", { + trackStudioEvent("studio_agent_deploy", { ...agentDeployCategories(args), + deploy_status: "succeeded", runtime_id: args.runtimeId, }); } export function trackAgentDeployFailed(args: AgentDeployFailedTelemetry): void { - trackStudioEvent("studio_agent_deploy_failed", { + trackStudioEvent("studio_agent_deploy", { ...agentDeployCategories(args), + deploy_status: "failed", failed_phase: args.phase, error_kind: agentDeployErrorKind(args.error, args.phase), + error_summary: telemetryErrorSummary(args.error), }); } export function trackSandboxCreateSucceeded( args: SandboxCreateSucceededTelemetry, ): void { - trackStudioEvent("studio_sandbox_create_succeeded", { + trackStudioEvent("studio_sandbox_create", { + sandbox_status: "succeeded", sandbox_kind: args.kind, sandbox_source: args.source, sandbox_session_id: args.sessionId, @@ -96,9 +217,145 @@ export function trackSandboxCreateSucceeded( } export function trackSandboxCreateFailed(args: SandboxCreateFailedTelemetry): void { - trackStudioEvent("studio_sandbox_create_failed", { + trackStudioEvent("studio_sandbox_create", { + sandbox_status: "failed", sandbox_kind: args.kind, sandbox_source: args.source, error_kind: sandboxCreateErrorKind(args.error), + error_summary: telemetryErrorSummary(args.error), }); } + +export function trackAgentDebugSucceeded(args: AgentDebugTelemetryBase): void { + trackStudioEvent( + "studio_agent_debug", + { + debug_status: "succeeded", + variant_type: args.variantType, + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentDebugFailed(args: AgentDebugFailedTelemetry): void { + trackStudioEvent( + "studio_agent_debug", + { + debug_status: "failed", + variant_type: args.variantType, + failed_phase: args.phase, + error_kind: agentDebugErrorKind(args.error), + error_summary: telemetryErrorSummary(args.error), + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentConnectSucceeded( + args: AgentConnectSucceededTelemetry, +): void { + trackStudioEvent( + "studio_agent_connect", + { + connect_status: "succeeded", + agent_kind: args.kind, + connect_source: args.source, + runtime_region: args.runtimeRegion, + runtime_is_mine: args.runtimeIsMine, + sandbox_status: args.sandboxStatus, + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentConnectFailed(args: AgentConnectFailedTelemetry): void { + trackStudioEvent( + "studio_agent_connect", + { + connect_status: "failed", + agent_kind: args.kind, + connect_source: args.source, + error_kind: agentConnectErrorKind(args.error), + error_summary: telemetryErrorSummary(args.error), + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentMessageSucceeded( + args: AgentMessageTelemetryBase, +): void { + trackStudioEvent( + "studio_agent_message", + { + message_status: "succeeded", + agent_kind: args.kind, + message_source: args.source, + session_state: args.sessionState, + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentMessageFailed(args: AgentMessageFailedTelemetry): void { + trackStudioEvent( + "studio_agent_message", + { + message_status: "failed", + agent_kind: args.kind, + message_source: args.source, + session_state: args.sessionState, + failed_phase: args.phase, + error_kind: agentMessageErrorKind(args.error), + error_summary: telemetryErrorSummary(args.error), + }, + { + duration_ms: args.durationMs, + }, + ); +} + +export function trackAgentSourceDownloadSucceeded( + args: AgentSourceDownloadSucceededTelemetry, +): void { + trackStudioEvent( + "studio_agent_source_download", + { + ...deploymentOriginCategories(args), + download_status: "succeeded", + }, + { + duration_ms: args.durationMs, + file_count: args.fileCount, + zip_size_bytes: args.zipSizeBytes, + }, + ); +} + +export function trackAgentSourceDownloadFailed( + args: AgentSourceDownloadFailedTelemetry, +): void { + trackStudioEvent( + "studio_agent_source_download", + { + ...deploymentOriginCategories(args), + download_status: "failed", + error_kind: agentSourceDownloadErrorKind(args.error), + error_summary: telemetryErrorSummary(args.error), + }, + { + duration_ms: args.durationMs, + file_count: args.fileCount, + }, + ); +} diff --git a/frontend/src/automations/feishu/FeishuBotIntegration.tsx b/frontend/src/automations/feishu/FeishuBotIntegration.tsx index 3bf5debf..e49b2eb3 100644 --- a/frontend/src/automations/feishu/FeishuBotIntegration.tsx +++ b/frontend/src/automations/feishu/FeishuBotIntegration.tsx @@ -12,6 +12,10 @@ import { type DeployAgentkitResult, type DeployStage, } from "../../adk/client"; +import { + trackAgentDeployFailed, + trackAgentDeploySucceeded, +} from "../../adk/telemetryEvents"; import feishuLogo from "../../assets/feishu-logo.svg"; import { agentNameProblem } from "../../create/agentNameValidation"; import { TextShimmer } from "../../ui/text-shimmer/TextShimmer"; @@ -101,6 +105,7 @@ export function FeishuBotIntegration({ onBack }: FeishuBotIntegrationProps) { const regionOptionRefs = useRef>([]); const regionFocusIndexRef = useRef(0); const taskIdRef = useRef(null); + const latestPhaseRef = useRef("prepare"); const cancelledRef = useRef(false); const mountedRef = useRef(true); @@ -166,6 +171,7 @@ export function FeishuBotIntegration({ onBack }: FeishuBotIntegrationProps) { const taskId = crypto.randomUUID(); taskIdRef.current = taskId; + latestPhaseRef.current = "prepare"; cancelledRef.current = false; setDeploymentStatus("preparing"); setActiveStage(null); @@ -179,18 +185,44 @@ export function FeishuBotIntegration({ onBack }: FeishuBotIntegrationProps) { region, taskId, onStage: (stage) => { + latestPhaseRef.current = stage.phase || "deploy"; if (!mountedRef.current || cancelledRef.current) return; setDeploymentStatus("running"); setActiveStage(stage); }, }); if (!mountedRef.current || cancelledRef.current) return; + trackAgentDeploySucceeded({ + telemetry: { + source: "feishu_automation", + createMode: "feishu_template", + aiAssisted: false, + }, + action: "create", + region, + networkType: "public", + feishuEnabled: true, + runtimeId: deployed.runtimeId || "", + }); setResult(deployed); setAppSecret(""); setShowSecret(false); setDeploymentStatus("succeeded"); } catch (error) { if (!mountedRef.current || cancelledRef.current) return; + trackAgentDeployFailed({ + telemetry: { + source: "feishu_automation", + createMode: "feishu_template", + aiAssisted: false, + }, + action: "create", + region, + networkType: "public", + feishuEnabled: true, + phase: latestPhaseRef.current, + error, + }); setDeploymentStatus("failed"); setDeployError(error instanceof Error ? error.message : String(error)); } finally { diff --git a/frontend/src/create/CodePackageCreate.tsx b/frontend/src/create/CodePackageCreate.tsx index 06653240..9b76b141 100644 --- a/frontend/src/create/CodePackageCreate.tsx +++ b/frontend/src/create/CodePackageCreate.tsx @@ -190,7 +190,11 @@ export function CodePackageCreate({ onNetworkChange={setNetwork} deployRegion={deployRegion} onDeployRegionChange={setDeployRegion} - deploymentTelemetrySource="code_package" + deploymentTelemetry={{ + source: "code_package", + createMode: "code_package", + aiAssisted: false, + }} onBack={onBack} backLabel="返回创建方式" deployDisabled={!project || reading} diff --git a/frontend/src/create/CustomCreate.tsx b/frontend/src/create/CustomCreate.tsx index e837a2f6..19527ab3 100644 --- a/frontend/src/create/CustomCreate.tsx +++ b/frontend/src/create/CustomCreate.tsx @@ -114,6 +114,11 @@ import { generateAgentProject, runGeneratedAgentTestSSE, } from "../adk/client"; +import { + trackAgentDebugFailed, + trackAgentDebugSucceeded, + type AgentDebugFailedPhase, +} from "../adk/telemetryEvents"; import type { DeployStage, GeneratedAgentTestRun, @@ -2473,6 +2478,8 @@ interface CustomCreateProps extends CreateModeProps { features?: UiFeatures; /** Publish deploy progress into the persistent app header. */ onDeploymentTaskChange?: (task: DeploymentTaskUpdate) => void; + /** Specific creation path inside the scratch flow. */ + createMode?: "custom" | "yaml_import"; /** Existing Runtime target when editing an Agent from the library. */ deploymentTarget?: { runtimeId: string; @@ -2500,6 +2507,7 @@ export function CustomCreate({ initialDraft, features, onDeploymentTaskChange, + createMode = "custom", deploymentTarget, initialDeployRegion = "cn-beijing", onDeploymentComplete, @@ -2516,6 +2524,7 @@ export function CustomCreate({ const [aiRequirement, setAiRequirement] = useState(""); const [aiGenerating, setAiGenerating] = useState(false); const [aiGenerated, setAiGenerated] = useState(false); + const [usedAiGeneration, setUsedAiGeneration] = useState(false); const [aiErrorDialog, setAiErrorDialog] = useState(null); const trimmedAiRequirement = aiRequirement.trim(); const aiRequirementError = @@ -2757,6 +2766,7 @@ export function CustomCreate({ setShowErrors(false); setBuildErr(""); setAiGenerated(true); + setUsedAiGeneration(true); } catch (error) { setAiErrorDialog( error instanceof Error ? error.message : String(error), @@ -3040,6 +3050,9 @@ export function CustomCreate({ setDebugInput(""); let createdRun: GeneratedAgentTestRun | null = null; + let failedPhase: AgentDebugFailedPhase | undefined; + const debugStartedAt = Date.now(); + const variantType = id === "baseline" ? "baseline" : "comparison"; try { await cleanupDebugVariantRun(id); await cleanupStoredDebugRuns(); @@ -3049,6 +3062,7 @@ export function CustomCreate({ description: variant.description, instruction: variant.instruction, }; + failedPhase = "create_test_run"; createdRun = await createGeneratedAgentTestRun( debugRuntimeDraft(variantDraft), deploymentTarget @@ -3059,6 +3073,7 @@ export function CustomCreate({ : undefined, ); rememberDebugTestRun(createdRun.runId); + failedPhase = "create_test_session"; const sessionId = await createGeneratedAgentTestSession( createdRun.runId, "test_user", @@ -3072,6 +3087,10 @@ export function CustomCreate({ : item, ), ); + trackAgentDebugSucceeded({ + durationMs: Date.now() - debugStartedAt, + variantType, + }); } catch (err) { if (createdRun) { try { @@ -3093,6 +3112,12 @@ export function CustomCreate({ : item, ), ); + trackAgentDebugFailed({ + durationMs: Date.now() - debugStartedAt, + variantType, + phase: failedPhase, + error: err, + }); } }; @@ -4104,7 +4129,11 @@ export function CustomCreate({ } deployRegion={deployRegion} onDeployRegionChange={setDeployRegion} - deploymentTelemetrySource="custom_create" + deploymentTelemetry={{ + source: "scratch", + createMode, + aiAssisted: usedAiGeneration, + }} onExportYaml={() => downloadText( `${draft.name || "agent"}.yaml`, diff --git a/frontend/src/create/IntelligentCreate.tsx b/frontend/src/create/IntelligentCreate.tsx index c7ef191e..2c63cc13 100644 --- a/frontend/src/create/IntelligentCreate.tsx +++ b/frontend/src/create/IntelligentCreate.tsx @@ -471,7 +471,11 @@ export function IntelligentCreate({ onDeploy={handleDeploy} onAgentAdded={onAgentAdded} onDeploymentTaskChange={onDeploymentTaskChange} - deploymentTelemetrySource="intelligent_create" + deploymentTelemetry={{ + source: "scratch", + createMode: "intelligent", + aiAssisted: true, + }} /> ) : (
diff --git a/frontend/src/ui/AgentSelector.tsx b/frontend/src/ui/AgentSelector.tsx index 6786e639..a082e7bc 100644 --- a/frontend/src/ui/AgentSelector.tsx +++ b/frontend/src/ui/AgentSelector.tsx @@ -30,6 +30,10 @@ import { type RuntimeDetail, } from "../adk/client"; import { connectRuntime } from "../adk/connections"; +import { + trackAgentConnectFailed, + trackAgentConnectSucceeded, +} from "../adk/telemetryEvents"; import { AgentIdentityIcon } from "./AgentIdentityIcon"; import { SkillCapabilityIcon, ToolCapabilityIcon } from "./CapabilityIcons"; import { RuntimeIdentityIcon } from "./RuntimeIdentityIcon"; @@ -279,13 +283,27 @@ export function AgentSelector({ (pageCache[page + 1] !== undefined || tokens[page + 1] !== undefined); function connect(rt: CloudRuntime) { + const startedAt = Date.now(); setConnecting(rt.runtimeId); connectRuntime(rt.runtimeId, rt.name, rt.region) .then(async (agentId) => { await onSelect(agentId); + trackAgentConnectSucceeded({ + kind: "runtime", + source: "navbar_picker", + durationMs: Date.now() - startedAt, + runtimeRegion: rt.region, + runtimeIsMine: rt.isMine, + }); onClose(); }) .catch((error) => { + trackAgentConnectFailed({ + kind: "runtime", + source: "navbar_picker", + durationMs: Date.now() - startedAt, + error, + }); if (error instanceof RuntimeAccessDeniedError) { setError(error.message); return; @@ -302,6 +320,27 @@ export function AgentSelector({ .finally(() => setConnecting(null)); } + async function selectLocalApp(app: string) { + const startedAt = Date.now(); + try { + await onSelect(app); + trackAgentConnectSucceeded({ + kind: "local", + source: "navbar_picker", + durationMs: Date.now() - startedAt, + }); + onClose(); + } catch (error) { + trackAgentConnectFailed({ + kind: "local", + source: "navbar_picker", + durationMs: Date.now() - startedAt, + error, + }); + setError(error instanceof Error ? error.message : String(error)); + } + } + if (!open) return null; // The visible set: the owner's full list (mineOnly) or the current lazy page, @@ -359,10 +398,7 @@ export function AgentSelector({