From 0fbb1f897e76b4d190ecb64cd48b35afff37c1cd Mon Sep 17 00:00:00 2001 From: huanghuoguoguo Date: Sun, 23 Aug 2026 20:32:51 +0800 Subject: [PATCH] feat(agent): add event debugging and streamline configuration --- .../pkg/api/http/controller/groups/agents.py | 18 + src/langbot/pkg/api/http/service/agent.py | 187 ++++++++- .../api/service/test_agent_service.py | 98 ++++- .../unit_tests/api/test_agents_controller.py | 63 ++- .../app/home/agents/AgentDetailContent.tsx | 77 +++- .../agents/components/AgentDebugPanel.tsx | 366 ++++++++++++++++++ .../agents/components/AgentFormComponent.tsx | 333 +++++++--------- web/src/app/home/bots/BotDetailContent.tsx | 14 +- .../home/bots/components/bot-form/BotForm.tsx | 6 +- .../dynamic-form/DynamicFormComponent.tsx | 10 +- web/src/app/infra/http/BackendClient.ts | 24 ++ web/src/i18n/locales/en-US.ts | 35 ++ web/src/i18n/locales/zh-Hans.ts | 32 ++ 13 files changed, 1035 insertions(+), 228 deletions(-) create mode 100644 web/src/app/home/agents/components/AgentDebugPanel.tsx diff --git a/src/langbot/pkg/api/http/controller/groups/agents.py b/src/langbot/pkg/api/http/controller/groups/agents.py index ee4c39984..0584d62a1 100644 --- a/src/langbot/pkg/api/http/controller/groups/agents.py +++ b/src/langbot/pkg/api/http/controller/groups/agents.py @@ -47,6 +47,24 @@ class AgentsRouterGroup(group.RouterGroup): async def _(request_context: RequestContext) -> str: return self.success(data=await self.ap.agent_service.get_agent_metadata(request_context)) + @self.route( + '//debug', + methods=['POST'], + auth_type=group.AuthType.USER_TOKEN_OR_API_KEY, + permission=Permission.RUNTIME_OPERATE, + ) + async def _(agent_uuid: str, request_context: RequestContext) -> str: + json_data = await quart.request.json + try: + result = await self.ap.agent_service.debug_agent( + request_context, + agent_uuid, + json_data or {}, + ) + except ValueError as exc: + return self.http_status(400, -1, str(exc)) + return self.success(data=result) + @self.route( '/', methods=['GET', 'PUT', 'DELETE'], diff --git a/src/langbot/pkg/api/http/service/agent.py b/src/langbot/pkg/api/http/service/agent.py index 4abeb74ce..df2271f29 100644 --- a/src/langbot/pkg/api/http/service/agent.py +++ b/src/langbot/pkg/api/http/service/agent.py @@ -1,15 +1,33 @@ from __future__ import annotations import datetime +import fnmatch +import time import uuid import typing import sqlalchemy +from langbot_plugin.api.entities.builtin.agent_runner.delivery import DeliveryContext +from langbot_plugin.api.entities.builtin.agent_runner.event import ( + ActorContext, + RawEventRef, + SubjectContext, +) +from langbot_plugin.api.entities.builtin.agent_runner.input import AgentInput from ....core import app from ....agent.runner.config_resolver import RunnerConfigResolver +from ....agent.runner.host_models import ( + AgentBinding, + AgentEventEnvelope, + BindingScope, + DeliveryPolicy, + StatePolicy, +) +from ....agent.runner.resource_policy import ResourcePolicyProjector from ....entity.persistence import agent as persistence_agent from ....workspace.errors import WorkspaceNotFoundError +from ..context import ExecutionContext, RequestContext from .tenant import TenantContext, require_workspace_uuid, scope_statement @@ -78,6 +96,173 @@ class AgentService: return None + async def debug_agent( + self, + context: RequestContext, + agent_uuid: str, + payload: dict[str, typing.Any], + ) -> dict[str, typing.Any]: + """Execute one synthetic event against a configured Agent. + + The debug surface uses a trusted Workspace execution context, never + delivers outputs to a real platform, and supports both message and + non-message event envelopes. + """ + agent = await self.get_agent(context, agent_uuid) + if agent is None or agent.get('kind') != AGENT_KIND_AGENT: + raise ValueError('Agent not found') + + event_type = str(payload.get('event_type') or 'message.received').strip() + if not event_type or len(event_type) > 128: + raise ValueError('Invalid event_type') + if not self._supports_event_type( + agent.get('supported_event_patterns'), + event_type, + ): + raise ValueError('Agent does not support this event type') + + text = str(payload.get('text') or '').strip() + if len(text) > 20_000: + raise ValueError('Debug input is too long') + event_data = payload.get('data') or {} + if not isinstance(event_data, dict): + raise ValueError('Debug event data must be an object') + + config = agent.get('config') + if not isinstance(config, dict): + raise ValueError('Agent configuration is invalid') + _, runner_id, runner_config = RunnerConfigResolver.resolve_agent_runner_config(config) + if not runner_id: + raise ValueError('Agent has no configured runner') + + conversation_id = str(payload.get('conversation_id') or f'debug:{agent_uuid}').strip() + if not conversation_id or len(conversation_id) > 256: + raise ValueError('Invalid debug conversation_id') + + actor_payload = payload.get('actor') or { + 'actor_type': 'user', + 'actor_id': 'debug-user', + 'actor_name': 'Debug User', + } + subject_payload = payload.get('subject') or { + 'subject_type': 'message' if event_type.startswith('message.') else event_type.split('.', 1)[0], + 'subject_id': 'debug-subject', + 'data': event_data, + } + if not isinstance(actor_payload, dict) or not isinstance(subject_payload, dict): + raise ValueError('Debug actor and subject must be objects') + + event_id = f'debug:{agent_uuid}:{uuid.uuid4()}' + event = AgentEventEnvelope( + event_id=event_id, + event_type=event_type, + event_time=int(time.time()), + source='webui', + source_event_type=event_type, + workspace_id=context.workspace_uuid, + conversation_id=conversation_id, + actor=ActorContext.model_validate(actor_payload), + subject=SubjectContext.model_validate(subject_payload), + input=AgentInput.model_validate( + { + 'text': text or event_type, + 'contents': [ + {'type': 'text', 'text': text or event_type}, + ], + 'attachments': [], + } + ), + delivery=DeliveryContext( + surface='webui', + reply_target=None, + supports_streaming=False, + supports_edit=False, + supports_reaction=False, + platform_capabilities={ + 'event_type': event_type, + 'debug': True, + }, + ), + raw_ref=RawEventRef(ref_id=event_id, storage_key=None), + data=event_data, + ) + binding = AgentBinding( + binding_id=f'debug:{agent_uuid}:{runner_id}', + scope=BindingScope(scope_type='agent', scope_id=agent_uuid), + event_types=[event_type], + runner_id=runner_id, + runner_config=runner_config, + resource_policy=ResourcePolicyProjector.from_runner_config(runner_config), + state_policy=StatePolicy( + state_scopes=['conversation', 'actor', 'subject', 'runner'], + ), + delivery_policy=DeliveryPolicy( + enable_streaming=False, + enable_reply=False, + enable_interactions=False, + ), + enabled=True, + agent_id=agent_uuid, + processor_type='agent', + processor_id=agent_uuid, + ) + execution_context = ExecutionContext.from_request( + context, + query_uuid=event_id, + ) + + output_items: list[dict[str, typing.Any]] = [] + final_text = '' + async for output in self.ap.agent_run_orchestrator.run( + event, + binding, + adapter_context={'_execution_context': execution_context}, + ): + output_text = self._provider_output_to_text(output) + if output_text: + final_text = output_text + output_items.append( + { + 'kind': output.__class__.__name__, + 'role': str(getattr(output, 'role', '') or ''), + 'text': output_text, + } + ) + + return { + 'event_id': event_id, + 'event_type': event_type, + 'conversation_id': conversation_id, + 'final_text': final_text, + 'outputs': output_items, + } + + @staticmethod + def _supports_event_type(patterns: typing.Any, event_type: str) -> bool: + normalized = patterns if isinstance(patterns, list) else AGENT_DEFAULT_EVENT_PATTERNS + return any(isinstance(pattern, str) and fnmatch.fnmatchcase(event_type, pattern) for pattern in normalized) + + @staticmethod + def _provider_output_to_text(output: typing.Any) -> str: + all_content = getattr(output, 'all_content', None) + if all_content: + return str(all_content) + content = getattr(output, 'content', None) + if content is None: + return '' + if isinstance(content, str): + return content + if isinstance(content, list): + parts: list[str] = [] + for item in content: + item_data = item.model_dump(mode='json') if hasattr(item, 'model_dump') else item + if isinstance(item_data, dict) and item_data.get('text') is not None: + parts.append(str(item_data['text'])) + elif item_data is not None and not isinstance(item_data, dict): + parts.append(str(item_data)) + return ''.join(parts) + return str(content) + async def create_agent(self, context: TenantContext, agent_data: dict) -> dict[str, str]: workspace_uuid = require_workspace_uuid(context) kind = agent_data.get('kind') or AGENT_KIND_AGENT @@ -89,7 +274,7 @@ class AgentService: 'description': agent_data.get('description') or '', 'emoji': agent_data.get('emoji') or '⚙️', 'config': {}, - } + }, ) return {'uuid': pipeline_uuid, 'kind': AGENT_KIND_PIPELINE} diff --git a/tests/unit_tests/api/service/test_agent_service.py b/tests/unit_tests/api/service/test_agent_service.py index 0d9bad7a6..c0e0e7486 100644 --- a/tests/unit_tests/api/service/test_agent_service.py +++ b/tests/unit_tests/api/service/test_agent_service.py @@ -111,9 +111,7 @@ class TestAgentServiceMetadata: ) metadata = await AgentService(app).get_agent_metadata(WORKSPACE_UUID) - app.pipeline_service.get_pipeline_metadata.assert_awaited_once_with( - WORKSPACE_UUID - ) + app.pipeline_service.get_pipeline_metadata.assert_awaited_once_with(WORKSPACE_UUID) assert metadata['runner_config'] == ai_metadata assert metadata['kinds'] == [ @@ -130,6 +128,88 @@ class TestAgentServiceMetadata: ] +class TestAgentServiceDebug: + async def test_debug_agent_runs_configured_runner_with_synthetic_event(self): + app = _make_app() + + async def run_agent(event, binding, adapter_context): + yield SimpleNamespace( + role='assistant', + content='debug result', + all_content=None, + ) + + app.agent_run_orchestrator = SimpleNamespace(run=Mock(side_effect=run_agent)) + service = AgentService(app) + service.get_agent = AsyncMock( + return_value={ + 'uuid': 'agent-1', + 'kind': AGENT_KIND_AGENT, + 'enabled': True, + 'supported_event_patterns': ['*'], + 'config': _agent_row().config, + } + ) + context = SimpleNamespace( + instance_uuid='instance-test', + workspace_uuid=WORKSPACE_UUID, + placement_generation=1, + principal=SimpleNamespace(account_uuid='account-test'), + entitlement_revision=0, + ) + + result = await service.debug_agent( + context, + 'agent-1', + { + 'event_type': 'group.member.joined', + 'text': 'A member joined.', + 'data': {'member_id': 'user-1'}, + 'conversation_id': 'debug-session', + }, + ) + + assert result['event_type'] == 'group.member.joined' + assert result['conversation_id'] == 'debug-session' + assert result['final_text'] == 'debug result' + assert result['outputs'] == [ + { + 'kind': 'SimpleNamespace', + 'role': 'assistant', + 'text': 'debug result', + } + ] + event, binding = app.agent_run_orchestrator.run.call_args.args + assert event.workspace_id == WORKSPACE_UUID + assert event.data == {'member_id': 'user-1'} + assert binding.agent_id == 'agent-1' + assert binding.runner_id == 'plugin:test/runner/default' + assert ( + app.agent_run_orchestrator.run.call_args.kwargs['adapter_context']['_execution_context'].workspace_uuid + == WORKSPACE_UUID + ) + + async def test_debug_agent_rejects_unsupported_event_type(self): + app = _make_app() + service = AgentService(app) + service.get_agent = AsyncMock( + return_value={ + 'uuid': 'agent-1', + 'kind': AGENT_KIND_AGENT, + 'supported_event_patterns': ['message.*'], + 'config': _agent_row().config, + } + ) + context = SimpleNamespace(workspace_uuid=WORKSPACE_UUID) + + with pytest.raises(ValueError, match='does not support'): + await service.debug_agent( + context, + 'agent-1', + {'event_type': 'group.member.joined'}, + ) + + class TestAgentServiceListAndLookup: async def test_get_agents_merges_agents_and_pipelines_without_leaking_config(self): app = _make_app() @@ -237,7 +317,7 @@ class TestAgentServiceCreateUpdateDelete: 'description': 'Handles support events', 'emoji': 'S', 'component_ref': 'plugin:caller/must-not-win/default', - } + }, ) insert_values = _compiled_params(app.persistence_mgr.execute_async.await_args.args[0]) @@ -309,7 +389,7 @@ class TestAgentServiceCreateUpdateDelete: 'runner': {'id': runner_id}, 'runner_config': {runner_id: {field_name: invalid_value}}, }, - } + }, ) app.persistence_mgr.execute_async.assert_not_awaited() @@ -334,7 +414,7 @@ class TestAgentServiceCreateUpdateDelete: } }, }, - } + }, ) app.persistence_mgr.execute_async.assert_not_awaited() @@ -352,7 +432,7 @@ class TestAgentServiceCreateUpdateDelete: 'runner': {'id': ''}, 'runner_config': {}, }, - } + }, ) insert_values = _compiled_params(app.persistence_mgr.execute_async.await_args.args[0]) @@ -491,7 +571,7 @@ class TestAgentServiceCreateUpdateDelete: 'name': 'Pipeline Agent', 'description': 'Legacy pipeline', 'emoji': 'P', - } + }, ) await service.update_agent( WORKSPACE_UUID, @@ -508,7 +588,7 @@ class TestAgentServiceCreateUpdateDelete: 'description': 'Legacy pipeline', 'emoji': 'P', 'config': {}, - } + }, ) app.pipeline_service.update_pipeline.assert_awaited_once_with( WORKSPACE_UUID, diff --git a/tests/unit_tests/api/test_agents_controller.py b/tests/unit_tests/api/test_agents_controller.py index b5253218b..224192c0a 100644 --- a/tests/unit_tests/api/test_agents_controller.py +++ b/tests/unit_tests/api/test_agents_controller.py @@ -41,12 +41,8 @@ async def _create_test_client(agent_service: SimpleNamespace): ap = SimpleNamespace( agent_service=agent_service, user_service=user_service, - apikey_service=SimpleNamespace( - authenticate_api_key=AsyncMock(return_value=None) - ), - workspace_collaboration_service=SimpleNamespace( - resolve_account_workspace=AsyncMock(return_value=access) - ), + apikey_service=SimpleNamespace(authenticate_api_key=AsyncMock(return_value=None)), + workspace_collaboration_service=SimpleNamespace(resolve_account_workspace=AsyncMock(return_value=access)), ) AgentsRouterGroup = import_module('langbot.pkg.api.http.controller.groups.agents').AgentsRouterGroup group = AgentsRouterGroup(ap, app) @@ -91,3 +87,58 @@ async def test_update_agent_returns_bad_request_for_invalid_runner_config(): 'agent-1', {'config': {'runner': {'id': 7}}}, ) + + +async def test_debug_agent_executes_with_runtime_permission(): + result = { + 'event_id': 'debug-event', + 'event_type': 'message.received', + 'conversation_id': 'debug-session', + 'final_text': 'hello', + 'outputs': [], + } + agent_service = SimpleNamespace(debug_agent=AsyncMock(return_value=result)) + client = await _create_test_client(agent_service) + + response = await client.post( + '/api/v1/agents/agent-1/debug', + json={ + 'event_type': 'message.received', + 'text': 'hello', + 'data': {}, + 'conversation_id': 'debug-session', + }, + headers={'Authorization': 'Bearer test-token'}, + ) + + assert response.status_code == 200 + assert (await response.get_json())['data'] == result + agent_service.debug_agent.assert_awaited_once_with( + ANY, + 'agent-1', + { + 'event_type': 'message.received', + 'text': 'hello', + 'data': {}, + 'conversation_id': 'debug-session', + }, + ) + + +async def test_debug_agent_returns_bad_request_for_invalid_event(): + agent_service = SimpleNamespace( + debug_agent=AsyncMock(side_effect=ValueError('Invalid event_type')), + ) + client = await _create_test_client(agent_service) + + response = await client.post( + '/api/v1/agents/agent-1/debug', + json={'event_type': ''}, + headers={'Authorization': 'Bearer test-token'}, + ) + + assert response.status_code == 400 + assert await response.get_json() == { + 'code': -1, + 'msg': 'Invalid event_type', + } diff --git a/web/src/app/home/agents/AgentDetailContent.tsx b/web/src/app/home/agents/AgentDetailContent.tsx index 5988f4066..92dd009bb 100644 --- a/web/src/app/home/agents/AgentDetailContent.tsx +++ b/web/src/app/home/agents/AgentDetailContent.tsx @@ -1,23 +1,31 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { Bug, Settings } from 'lucide-react'; import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { httpClient } from '@/app/infra/http/HttpClient'; +import { useCurrentWorkspace } from '@/app/infra/http'; import { Agent } from '@/app/infra/entities/api'; import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext'; import PipelineDetailContent from '@/app/home/pipelines/PipelineDetailContent'; import AgentCreateContent from './components/AgentCreateContent'; +import AgentDebugPanel from './components/AgentDebugPanel'; import AgentFormComponent from './components/AgentFormComponent'; export default function AgentDetailContent({ id }: { id: string }) { const isCreateMode = id === 'new'; const navigate = useNavigate(); const { t } = useTranslation(); + const currentWorkspace = useCurrentWorkspace(); + const canOperate = + currentWorkspace?.permissions.includes('runtime.operate') ?? false; const { refreshPipelines, pipelines, setDetailEntityName } = useSidebarData(); const [agent, setAgent] = useState(null); const [loading, setLoading] = useState(!isCreateMode); const [formDirty, setFormDirty] = useState(false); const [formSaving, setFormSaving] = useState(false); + const [activeTab, setActiveTab] = useState('config'); useEffect(() => { if (isCreateMode) { @@ -71,32 +79,71 @@ export default function AgentDetailContent({ id }: { id: string }) { } return ( -
+

{t('agents.editAgent')}

-
- { - refreshPipelines(); - }} - onDeleted={() => { - refreshPipelines(); - navigate('/home/agents'); - }} - onDirtyChange={setFormDirty} - onSavingChange={setFormSaving} - /> -
+ + + + + {t('pipelines.configuration')} + + {canOperate && ( + + + {t('agents.debugTab')} + + )} + + + + { + refreshPipelines(); + }} + onDeleted={() => { + refreshPipelines(); + navigate('/home/agents'); + }} + onDirtyChange={setFormDirty} + onSavingChange={setFormSaving} + /> + + + {canOperate && ( + + + + )} +
); } diff --git a/web/src/app/home/agents/components/AgentDebugPanel.tsx b/web/src/app/home/agents/components/AgentDebugPanel.tsx new file mode 100644 index 000000000..014ead258 --- /dev/null +++ b/web/src/app/home/agents/components/AgentDebugPanel.tsx @@ -0,0 +1,366 @@ +import { useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import { + AlertTriangle, + Braces, + LoaderCircle, + MessageSquare, + Play, + RotateCcw, +} from 'lucide-react'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Textarea } from '@/components/ui/textarea'; + +interface AgentDebugPanelProps { + agentId: string; + supportedEventPatterns?: string[]; +} + +interface DebugEntry { + id: string; + direction: 'input' | 'output' | 'error'; + eventType: string; + text: string; +} + +const EVENT_PRESETS = [ + { + value: 'message.received', + labelKey: 'agents.debugMessageReceived', + text: '', + data: {}, + }, + { + value: 'group.member.joined', + labelKey: 'agents.debugGroupMemberJoined', + text: 'A new member joined the group.', + data: { + group_id: 'debug-group', + member_id: 'debug-user', + member_name: 'Debug User', + }, + }, + { + value: 'group.member.left', + labelKey: 'agents.debugGroupMemberLeft', + text: 'A member left the group.', + data: { + group_id: 'debug-group', + member_id: 'debug-user', + member_name: 'Debug User', + }, + }, + { + value: 'friend.requested', + labelKey: 'agents.debugFriendRequested', + text: 'A user sent a friend request.', + data: { + requester_id: 'debug-user', + requester_name: 'Debug User', + message: 'Hello', + }, + }, + { + value: 'feedback.received', + labelKey: 'agents.debugFeedbackReceived', + text: 'The user submitted feedback.', + data: { + rating: 5, + content: 'Debug feedback', + }, + }, + { + value: 'custom', + labelKey: 'agents.debugCustomEvent', + text: '', + data: {}, + }, +] as const; + +function createDebugSessionId(agentId: string) { + const nonce = globalThis.crypto?.randomUUID?.() ?? String(Date.now()); + return `webui:${agentId}:${nonce}`; +} + +export default function AgentDebugPanel({ + agentId, + supportedEventPatterns = ['*'], +}: AgentDebugPanelProps) { + const { t } = useTranslation(); + const [preset, setPreset] = useState('message.received'); + const [customEventType, setCustomEventType] = useState('custom.event'); + const [inputText, setInputText] = useState(''); + const [eventDataText, setEventDataText] = useState('{}'); + const [running, setRunning] = useState(false); + const [entries, setEntries] = useState([]); + const sessionIdRef = useRef(createDebugSessionId(agentId)); + + const eventType = preset === 'custom' ? customEventType.trim() : preset; + const isMessageEvent = eventType.startsWith('message.'); + const supportedLabel = useMemo( + () => supportedEventPatterns.join(', '), + [supportedEventPatterns], + ); + + function selectPreset(value: string) { + setPreset(value); + const nextPreset = EVENT_PRESETS.find((item) => item.value === value); + if (!nextPreset) return; + setInputText(nextPreset.text); + setEventDataText(JSON.stringify(nextPreset.data, null, 2)); + } + + function resetSession() { + sessionIdRef.current = createDebugSessionId(agentId); + setEntries([]); + } + + async function runDebugEvent() { + if (!eventType) { + toast.error(t('agents.debugEventTypeRequired')); + return; + } + if (isMessageEvent && !inputText.trim()) { + toast.error(t('agents.debugInputRequired')); + return; + } + + let eventData: Record; + try { + const parsed = JSON.parse(eventDataText || '{}'); + if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') { + throw new Error('payload must be an object'); + } + eventData = parsed as Record; + } catch { + toast.error(t('agents.debugInvalidPayload')); + return; + } + + const requestId = globalThis.crypto?.randomUUID?.() ?? String(Date.now()); + setEntries((current) => [ + ...current, + { + id: `input:${requestId}`, + direction: 'input', + eventType, + text: inputText.trim() || JSON.stringify(eventData, null, 2), + }, + ]); + setRunning(true); + try { + const result = await httpClient.debugAgent(agentId, { + event_type: eventType, + text: inputText.trim(), + data: eventData, + conversation_id: sessionIdRef.current, + }); + setEntries((current) => [ + ...current, + { + id: `output:${result.event_id}`, + direction: 'output', + eventType, + text: result.final_text || t('agents.debugNoTextOutput'), + }, + ]); + if (isMessageEvent) setInputText(''); + } catch (error) { + const message = + typeof error === 'object' && error && 'msg' in error + ? String((error as { msg?: string }).msg || '') + : t('agents.debugRunFailed'); + setEntries((current) => [ + ...current, + { + id: `error:${requestId}`, + direction: 'error', + eventType, + text: message || t('agents.debugRunFailed'), + }, + ]); + } finally { + setRunning(false); + } + } + + return ( +
+ + +
+
+ + {isMessageEvent ? ( + + ) : ( + + )} + {t('agents.debugTitle')} + + {t('agents.debugDescription')} +
+ +
+
+ + + + {t('agents.debugActualRun')} + + {t('agents.debugActualRunDescription')} + + + +
+
+ + +
+ {preset === 'custom' && ( +
+ + setCustomEventType(event.target.value)} + placeholder="custom.event" + /> +
+ )} +
+ +
+ +