diff --git a/docs/design/automations-ux-rework/status.md b/docs/design/automations-ux-rework/status.md
index fa07a09fb6..3132a92fc7 100644
--- a/docs/design/automations-ux-rework/status.md
+++ b/docs/design/automations-ux-rework/status.md
@@ -2,7 +2,7 @@
## Current state
-Checkpoints 1 and 2 implemented in code and linted. Checkpoint 3 not started.
+Checkpoints 1, 2, and 3 implemented in code. Checkpoint 3 logs UI redesigned for clarity.
## Checkpoint 1: Remove gate, auto-ping, restore test button
@@ -26,6 +26,18 @@ Checkpoints 1 and 2 implemented in code and linted. Checkpoint 3 not started.
| 2.5 Add `testDraftAutomationAtom` | Completed | Drawer uses a dedicated draft test atom. |
| 2.6 Enable test button for drafts in drawer | Completed | Test now uses current form values in both create and edit mode. |
+## Checkpoint 3: Delivery logs in the drawer
+
+| Task | Status | Notes |
+|------|--------|-------|
+| 3.1 Add frontend delivery query client | Completed | `queryWebhookDeliveries` added to the automations API client. |
+| 3.2 Add delivery query atom family | Completed | Delivery logs are fetched per subscription via `automationDeliveriesAtomFamily`. |
+| 3.3 Build logs tab UI | Completed | Drawer now renders a `Logs` tab for persisted automations. |
+| 3.4 Delivery detail view | Completed | Simplified: removed overview/JSON tabs, now shows raw JSON directly via `SimpleSharedEditor`. |
+| 3.5 Constrain scrolling to the list/detail panes | Completed | Prevented the full drawer tab from growing/scrolling when logs increase; the delivery list now owns list overflow while the JSON editor scrolls internally. |
+| 3.6 Redesign delivery list items | Completed | Replaced opaque button rows with `ListItem`-pattern styling: left-border accent on selection, status dots, design-token colors (`bgColors`, `textColors`, `borderColors` from `@agenta/ui`), keyboard navigation, hover feedback. |
+| 3.7 Widen drawer layout | Completed | Drawer width increased to accommodate config and log inspection. |
+
## Decisions log
| Date | Decision | Rationale |
@@ -36,3 +48,7 @@ Checkpoints 1 and 2 implemented in code and linted. Checkpoint 3 not started.
| 2026-03-13 | Table status stays `Active` in checkpoint 1 | Avoids implying that saved automations are blocked before the log UI exists. |
| 2026-03-13 | Test-draft bypasses event bus entirely | Direct HTTP call via extracted `execute_webhook_request`. No persisted subscription needed. |
| 2026-03-13 | Test button always tests form values | In edit mode, draft testing uses the current form state rather than the persisted subscription. |
+| 2026-03-13 | Logs live only on persisted automations | Draft tests are ephemeral, so the drawer exposes logs only after a subscription has been created. |
+| 2026-03-13 | Simplify delivery detail to raw JSON only | Overview tab with separate fields added unnecessary complexity; raw JSON is more useful for debugging. |
+| 2026-03-13 | Use design tokens for list items | Replaced hard-coded `bg-white` / CSS variable approach with `@agenta/ui` tokens for theme consistency. |
+| 2026-03-13 | Deliveries list loads the newest 25 logs | `queryWebhookDeliveries` already supports `Windowing`, but the current drawer uses a simple first page (`limit=25`, `order=descending`) until we add explicit pagination/load-more UX. |
diff --git a/web/oss/src/components/Automations/AutomationDrawer.tsx b/web/oss/src/components/Automations/AutomationDrawer.tsx
index 3a57302283..b379f91272 100644
--- a/web/oss/src/components/Automations/AutomationDrawer.tsx
+++ b/web/oss/src/components/Automations/AutomationDrawer.tsx
@@ -1,7 +1,7 @@
import {createElement, useCallback, useEffect, useMemo, useState} from "react"
import {BookOpen} from "@phosphor-icons/react"
-import {Button, Collapse, Form, Input, message, Select, Tooltip, Typography} from "antd"
+import {Button, Collapse, Form, Input, message, Select, Tabs, Tooltip, Typography} from "antd"
import {useAtom, useSetAtom} from "jotai"
import EnhancedDrawer from "@/oss/components/EnhancedUIs/Drawer"
@@ -25,6 +25,7 @@ import {
import {AUTOMATION_SCHEMA, EVENT_OPTIONS} from "./assets/constants"
import {AutomationFieldRenderer} from "./AutomationFieldRenderer"
+import AutomationLogsTab from "./AutomationLogsTab"
import {RequestPreview} from "./RequestPreview"
import {buildSubscription} from "./utils/buildSubscription"
import {AUTOMATION_TEST_FAILURE_MESSAGE, handleTestResult} from "./utils/handleTestResult"
@@ -33,6 +34,7 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
const [form] = Form.useForm()
const [open, setOpen] = useAtom(isAutomationDrawerOpenAtom)
const [initialValues, setEditingWebhook] = useAtom(editingAutomationAtom)
+ const [activeTab, setActiveTab] = useState("configuration")
const [isTesting, setIsTesting] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const setCreatedWebhookSecret = useSetAtom(createdWebhookSecretAtom)
@@ -52,10 +54,13 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
useEffect(() => {
if (!open) {
+ setActiveTab("configuration")
form.resetFields()
return
}
+ setActiveTab("configuration")
+
if (initialValues) {
// Determine provider via heuristic since no meta field is stored.
let isGitHub = false
@@ -156,6 +161,13 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
try {
setIsTesting(true)
+
+ if (activeTab === "logs" && initialValues?.id) {
+ const response = await testAutomation(initialValues.id)
+ handleTestResult(response)
+ return
+ }
+
const {payload} = await buildPayloadFromForm()
const response = await testDraftAutomation(payload)
handleTestResult(response)
@@ -166,7 +178,14 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
} finally {
setIsTesting(false)
}
- }, [buildPayloadFromForm, open, testDraftAutomation])
+ }, [
+ activeTab,
+ buildPayloadFromForm,
+ initialValues?.id,
+ open,
+ testAutomation,
+ testDraftAutomation,
+ ])
const handleOk = useCallback(async () => {
try {
@@ -258,6 +277,127 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
? "https://agenta.ai/docs/prompt-engineering/integrating-prompts/github"
: "https://agenta.ai/docs/prompt-engineering/integrating-prompts/webhooks"
+ const drawerTabs = useMemo(
+ () => [
+ {
+ key: "configuration",
+ label: "Configuration",
+ children: (
+
+
+ Set up an automation to trigger external services when specific events
+ occur within Agenta.
+
+
+
+
+ ),
+ },
+ ...(initialValues?.id
+ ? [
+ {
+ key: "logs",
+ label: "Logs",
+ children:
+ activeTab === "logs" ? (
+
+ ) : null,
+ },
+ ]
+ : []),
+ ],
+ [
+ activeTab,
+ form,
+ initialValues?.id,
+ isEdit,
+ providerOptions,
+ selectedProviderConfig,
+ setSelectedProvider,
+ ],
+ )
+
return (
<>
void}) => {
}
open={open}
onClose={onCancel}
- width={450}
+ width={840}
destroyOnHidden
footer={
@@ -297,78 +437,14 @@ const AutomationDrawer = ({onSuccess}: {onSuccess: () => void}) => {
}
>
-
- Set up an automation to trigger external services when specific events occur
- within Agenta.
+
+
-
-
>
)
diff --git a/web/oss/src/components/Automations/AutomationLogsTab.tsx b/web/oss/src/components/Automations/AutomationLogsTab.tsx
new file mode 100644
index 0000000000..de9c03bba2
--- /dev/null
+++ b/web/oss/src/components/Automations/AutomationLogsTab.tsx
@@ -0,0 +1,180 @@
+import {useEffect, useMemo, useState} from "react"
+
+import {bgColors, borderColors, cn, textColors} from "@agenta/ui"
+import {Empty, Skeleton} from "antd"
+import {useAtomValue} from "jotai"
+
+import SimpleSharedEditor from "@/oss/components/EditorViews/SimpleSharedEditor"
+import {WebhookDelivery} from "@/oss/services/automations/types"
+import {automationDeliveriesAtomFamily} from "@/oss/state/automations/atoms"
+
+const formatTimestamp = (value?: string) => {
+ if (!value) return "-"
+ const parsed = new Date(value)
+ if (Number.isNaN(parsed.getTime())) return value
+ return parsed.toLocaleString()
+}
+
+const getStatusCode = (delivery: WebhookDelivery) => {
+ return delivery.data?.response?.status_code || delivery.status.code || "-"
+}
+
+const isDeliverySuccess = (delivery: WebhookDelivery) => {
+ return delivery.status.message === "success"
+}
+
+const DeliveryListItem = ({
+ delivery,
+ isSelected,
+ onClick,
+}: {
+ delivery: WebhookDelivery
+ isSelected: boolean
+ onClick: () => void
+}) => {
+ const success = isDeliverySuccess(delivery)
+ const statusCode = getStatusCode(delivery)
+
+ return (
+
{
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault()
+ onClick()
+ }
+ }}
+ >
+
+
+
+ {success ? "Success" : "Failed"}
+
+ ยท
+ {statusCode}
+
+
+ {delivery.data?.event_type || "Webhook Test"}
+
+
+ {formatTimestamp(delivery.created_at)}
+
+
+ )
+}
+
+export const AutomationLogsTab = ({subscriptionId}: {subscriptionId: string}) => {
+ const {data: deliveries, isPending} = useAtomValue(
+ automationDeliveriesAtomFamily(subscriptionId),
+ )
+ const [selectedDeliveryId, setSelectedDeliveryId] = useState
(null)
+
+ useEffect(() => {
+ if (!deliveries?.length) {
+ setSelectedDeliveryId(null)
+ return
+ }
+
+ if (
+ !selectedDeliveryId ||
+ !deliveries.some((delivery) => delivery.id === selectedDeliveryId)
+ ) {
+ setSelectedDeliveryId(deliveries[0].id)
+ }
+ }, [deliveries, selectedDeliveryId])
+
+ const selectedDelivery = useMemo(
+ () => deliveries?.find((delivery) => delivery.id === selectedDeliveryId) ?? null,
+ [deliveries, selectedDeliveryId],
+ )
+
+ const deliveryJson = useMemo(() => {
+ if (!selectedDelivery) return ""
+ return JSON.stringify(selectedDelivery, null, 2)
+ }, [selectedDelivery])
+
+ if (isPending) {
+ return
+ }
+
+ if (!deliveries?.length) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ {/* Delivery list */}
+
+ {deliveries.map((delivery) => (
+ setSelectedDeliveryId(delivery.id)}
+ />
+ ))}
+
+
+ {/* Detail: raw JSON */}
+
+ {selectedDelivery ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ )
+}
+
+export default AutomationLogsTab
diff --git a/web/oss/src/services/automations/api.ts b/web/oss/src/services/automations/api.ts
index 9c94c6cf41..7662826c7a 100644
--- a/web/oss/src/services/automations/api.ts
+++ b/web/oss/src/services/automations/api.ts
@@ -2,6 +2,8 @@ import axios from "@/oss/lib/api/assets/axiosConfig"
import {getAgentaApiUrl} from "@/oss/lib/helpers/api"
import {
+ WebhookDeliveriesQueryRequest,
+ WebhookDeliveriesResponse,
WebhookSubscriptionDraftTestRequest,
WebhookSubscriptionCreateRequest,
WebhookSubscriptionEditRequest,
@@ -60,9 +62,17 @@ const testWebhookDraft = async (
return response.data
}
+const queryWebhookDeliveries = async (
+ data: WebhookDeliveriesQueryRequest,
+): Promise => {
+ const response = await axios.post(`${getAgentaApiUrl()}/webhooks/deliveries/query`, data)
+ return response.data
+}
+
export {
createWebhookSubscription,
deleteWebhookSubscription,
+ queryWebhookDeliveries,
queryWebhookSubscriptions,
testWebhookDraft,
testWebhookSubscription,
diff --git a/web/oss/src/services/automations/types.ts b/web/oss/src/services/automations/types.ts
index a32877c82a..9237cfc238 100644
--- a/web/oss/src/services/automations/types.ts
+++ b/web/oss/src/services/automations/types.ts
@@ -103,6 +103,22 @@ export type WebhookSubscriptionDraftTestRequest =
| WebhookSubscriptionCreateRequest
| WebhookSubscriptionEditRequest
+export interface WebhookDeliveriesQueryRequest {
+ delivery?: {
+ subscription_id?: string
+ event_id?: string
+ status?: {
+ code?: string
+ }
+ }
+ include_archived?: boolean
+ windowing?: {
+ limit?: number
+ order?: "ascending" | "descending"
+ cursor?: string
+ }
+}
+
// --- RESPONSE SHAPES ------------------------------------------------------- //
export interface WebhookSubscriptionResponse {
diff --git a/web/oss/src/state/automations/atoms.ts b/web/oss/src/state/automations/atoms.ts
index e5ddb3f653..e83640191d 100644
--- a/web/oss/src/state/automations/atoms.ts
+++ b/web/oss/src/state/automations/atoms.ts
@@ -1,10 +1,12 @@
import {atom} from "jotai"
+import {atomFamily} from "jotai/utils"
import {atomWithQuery} from "jotai-tanstack-query"
import {queryClient} from "@/oss/lib/api/queryClient"
import {
createWebhookSubscription,
deleteWebhookSubscription,
+ queryWebhookDeliveries,
queryWebhookSubscriptions,
testWebhookDraft,
testWebhookSubscription,
@@ -33,6 +35,36 @@ export const automationsAtom = atomWithQuery((get) => {
}
})
+export const automationDeliveriesAtomFamily = atomFamily((webhookSubscriptionId: string | null) =>
+ atomWithQuery((get) => {
+ const projectId = get(projectIdAtom)
+
+ return {
+ queryKey: ["automation-deliveries", projectId, webhookSubscriptionId],
+ queryFn: async () => {
+ if (!webhookSubscriptionId) {
+ return []
+ }
+
+ const response = await queryWebhookDeliveries({
+ delivery: {
+ subscription_id: webhookSubscriptionId,
+ },
+ windowing: {
+ limit: 25,
+ order: "descending",
+ },
+ })
+ return response.deliveries
+ },
+ staleTime: 30_000,
+ refetchOnWindowFocus: false,
+ refetchOnReconnect: false,
+ enabled: !!projectId && !!webhookSubscriptionId,
+ }
+ }),
+)
+
export const createAutomationAtom = atom(
null,
async (_get, _set, payload: WebhookSubscriptionCreateRequest) => {
@@ -69,6 +101,7 @@ export const deleteAutomationAtom = atom(
export const testAutomationAtom = atom(null, async (_get, _set, webhookSubscriptionId: string) => {
const res = await testWebhookSubscription(webhookSubscriptionId)
await queryClient.invalidateQueries({queryKey: ["automations"]})
+ await queryClient.invalidateQueries({queryKey: ["automation-deliveries"]})
return res
})