[feat] Add triggers (connections, subscriptions, schedules)#4749
[feat] Add triggers (connections, subscriptions, schedules)#4749jp-agenta wants to merge 20 commits into
Conversation
Inbound dual of webhooks: turn external provider events into Agenta workflow runs. Adds a shared routerless connections domain (core/gateway/connections), a triggers domain (event catalog, subscriptions, deliveries), a global Composio ingress endpoint with HMAC verification + async dispatch worker, and the web UI for catalog browse and subscription/delivery management. Includes design docs and unit/acceptance tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR implements Gateway Triggers, a new first-class inbound event domain for Agenta. It extracts provider connection handling into a shared Changes
Sequence Diagram(s)sequenceDiagram
participant Composio as Composio<br/>(Provider)
participant Ingress as TriggersRouter<br/>(HTTP Ingress)
participant Worker as TriggersWorker<br/>(TaskIQ)
participant Dispatcher as TriggersDispatcher<br/>(Asyncio)
participant Workflows as WorkflowsService<br/>(Invocation)
participant DAO as TriggersDAO<br/>(Persistence)
Composio->>Ingress: POST /triggers/composio/events/<br/>(signed event envelope)
Ingress->>Ingress: verify HMAC signature
Ingress->>Ingress: extract metadata.trigger_id
Ingress->>Worker: enqueue(trigger_id, event_id, event)
Ingress-->>Composio: 202 Accepted
Worker->>DAO: get_project_and_subscription_by_trigger_id
DAO-->>Worker: (project_id, subscription)
Worker->>Dispatcher: dispatch(project_id, entity, event_id, event)
Dispatcher->>Dispatcher: check entity.flags.is_active
Dispatcher->>Dispatcher: dedup_seen(event_id)
Dispatcher->>Workflows: invoke_workflow(...)
Workflows-->>Dispatcher: response (status, outputs)
Dispatcher->>DAO: write_delivery(TriggerDeliveryCreate)
DAO-->>Dispatcher: delivery persisted
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Railway Preview Environment
|
get_default_workspace_id no longer prefers owner-role (multi-org: an invitee owns their own empty personal workspace). Assert oldest membership wins regardless of role. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g in tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 11
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
api/ee/tests/pytest/acceptance/triggers/test_triggers_subscriptions.py (1)
182-227:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuarantee connection cleanup even on test failure.
If any assertion fails before Line 226, the connection cleanup is skipped, which can leak external/provider state and make later acceptance runs flaky. Move cleanup to a
finallyblock and assert the delete response.Suggested fix
def test_create_list_disable_delete_keeps_connection(self, triggers_api): connection_id = self._create_connection(triggers_api) - - create = triggers_api( - "POST", - "/triggers/subscriptions/", - json={ - "subscription": { - "name": f"sub-{uuid4().hex[:8]}", - "connection_id": connection_id, - "data": { - "event_key": "GITHUB_STAR_ADDED_EVENT", - "trigger_config": {}, - "inputs_fields": {"repo": "$.event.data.repository"}, - "references": {"workflow": {"slug": "triage"}}, - }, - } - }, - ) - assert create.status_code == 200, create.text - sub = create.json()["subscription"] - subscription_id = sub["id"] - assert sub["connection_id"] == connection_id - assert sub["data"]["ti_id"] is not None - - listing = triggers_api("GET", "/triggers/subscriptions/").json() - assert any(s["id"] == subscription_id for s in listing["subscriptions"]) - - revoke = triggers_api( - "POST", f"/triggers/subscriptions/{subscription_id}/revoke" - ) - assert revoke.status_code == 200, revoke.text - assert revoke.json()["subscription"]["enabled"] is False - - delete = triggers_api("DELETE", f"/triggers/subscriptions/{subscription_id}") - assert delete.status_code == 204 - - fetch = triggers_api("GET", f"/triggers/subscriptions/{subscription_id}") - assert fetch.status_code == 404 - - # C7: deleting the subscription must NOT delete/revoke the connection. - conn = triggers_api("GET", f"/tools/connections/{connection_id}") - assert conn.status_code == 200, conn.text - - triggers_api("DELETE", f"/tools/connections/{connection_id}") + try: + create = triggers_api( + "POST", + "/triggers/subscriptions/", + json={ + "subscription": { + "name": f"sub-{uuid4().hex[:8]}", + "connection_id": connection_id, + "data": { + "event_key": "GITHUB_STAR_ADDED_EVENT", + "trigger_config": {}, + "inputs_fields": {"repo": "$.event.data.repository"}, + "references": {"workflow": {"slug": "triage"}}, + }, + } + }, + ) + assert create.status_code == 200, create.text + sub = create.json()["subscription"] + subscription_id = sub["id"] + assert sub["connection_id"] == connection_id + assert sub["data"]["ti_id"] is not None + + listing = triggers_api("GET", "/triggers/subscriptions/").json() + assert any(s["id"] == subscription_id for s in listing["subscriptions"]) + + revoke = triggers_api("POST", f"/triggers/subscriptions/{subscription_id}/revoke") + assert revoke.status_code == 200, revoke.text + assert revoke.json()["subscription"]["enabled"] is False + + delete = triggers_api("DELETE", f"/triggers/subscriptions/{subscription_id}") + assert delete.status_code == 204 + + fetch = triggers_api("GET", f"/triggers/subscriptions/{subscription_id}") + assert fetch.status_code == 404 + + conn = triggers_api("GET", f"/tools/connections/{connection_id}") + assert conn.status_code == 200, conn.text + finally: + cleanup = triggers_api("DELETE", f"/tools/connections/{connection_id}") + assert cleanup.status_code == 204, cleanup.textapi/oss/tests/pytest/acceptance/triggers/test_triggers_subscriptions.py (1)
108-156:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake lifecycle cleanup unconditional.
Cleanup at Line 155 only runs on the happy path. A mid-test failure leaves shared connection state behind, which can pollute subsequent acceptance runs. Move deletion to
finallyand assert cleanup success.Suggested fix
def test_create_list_disable_delete_keeps_connection(self, authed_api): connection_id = self._create_connection(authed_api) - - # CREATE — binds the event to a workflow reference on the shared connection - create = authed_api( - "POST", - "/triggers/subscriptions/", - json={ - "subscription": { - "name": f"sub-{uuid4().hex[:8]}", - "connection_id": connection_id, - "data": { - "event_key": "GITHUB_STAR_ADDED_EVENT", - "trigger_config": {}, - "inputs_fields": {"repo": "$.event.data.repository"}, - "references": {"workflow": {"slug": "triage"}}, - }, - } - }, - ) - assert create.status_code == 200, create.text - sub = create.json()["subscription"] - subscription_id = sub["id"] - assert sub["connection_id"] == connection_id - assert sub["data"]["ti_id"] is not None - assert sub["enabled"] is True - - # LIST - listing = authed_api("GET", "/triggers/subscriptions/").json() - assert any(s["id"] == subscription_id for s in listing["subscriptions"]) - - # DISABLE (revoke the subscription, not the connection) - revoke = authed_api("POST", f"/triggers/subscriptions/{subscription_id}/revoke") - assert revoke.status_code == 200, revoke.text - assert revoke.json()["subscription"]["enabled"] is False - - # DELETE - delete = authed_api("DELETE", f"/triggers/subscriptions/{subscription_id}") - assert delete.status_code == 204 - - fetch = authed_api("GET", f"/triggers/subscriptions/{subscription_id}") - assert fetch.status_code == 404 - - # C7: deleting the subscription must NOT delete/revoke the connection. - conn = authed_api("GET", f"/tools/connections/{connection_id}") - assert conn.status_code == 200, conn.text - - authed_api("DELETE", f"/tools/connections/{connection_id}") + try: + create = authed_api( + "POST", + "/triggers/subscriptions/", + json={ + "subscription": { + "name": f"sub-{uuid4().hex[:8]}", + "connection_id": connection_id, + "data": { + "event_key": "GITHUB_STAR_ADDED_EVENT", + "trigger_config": {}, + "inputs_fields": {"repo": "$.event.data.repository"}, + "references": {"workflow": {"slug": "triage"}}, + }, + } + }, + ) + assert create.status_code == 200, create.text + sub = create.json()["subscription"] + subscription_id = sub["id"] + assert sub["connection_id"] == connection_id + assert sub["data"]["ti_id"] is not None + assert sub["enabled"] is True + + listing = authed_api("GET", "/triggers/subscriptions/").json() + assert any(s["id"] == subscription_id for s in listing["subscriptions"]) + + revoke = authed_api("POST", f"/triggers/subscriptions/{subscription_id}/revoke") + assert revoke.status_code == 200, revoke.text + assert revoke.json()["subscription"]["enabled"] is False + + delete = authed_api("DELETE", f"/triggers/subscriptions/{subscription_id}") + assert delete.status_code == 204 + + fetch = authed_api("GET", f"/triggers/subscriptions/{subscription_id}") + assert fetch.status_code == 404 + + conn = authed_api("GET", f"/tools/connections/{connection_id}") + assert conn.status_code == 200, conn.text + finally: + cleanup = authed_api("DELETE", f"/tools/connections/{connection_id}") + assert cleanup.status_code == 204, cleanup.textdocs/designs/gateway-triggers/proposal.md (1)
285-289:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove the empty quoted line.
The blank
>line before the next heading trips MD028 and will keep docs lint failing.As per the markdownlint hint, this blockquote needs to stay contiguous.
🛠️ Proposed fix
> **Consequence — cross-domain revoke.** Because `ca_*` is shared, revoking it affects > both tools actions and trigger subscriptions on it. Lean: revoke-for-everyone + show > usage; deleting a subscription must not revoke the connection. Connect once, used > everywhere — the inverse of the connect-twice cost that rejected option B carried. -> ### The workflow dispatch seamSource: Linters/SAST tools
🟡 Minor comments (16)
web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscriptions.ts-54-58 (1)
54-58:⚠️ Potential issue | 🟡 MinorGate loading state for empty
connectionIdto match the pattern inuseTriggerDeliveries.Line 57 exposes loading while the query is disabled. When
connectionIdis empty, the query is disabled (enabled: !!connectionIdat line 42) butisLoadingstill reflectsquery.isPending. Apply the same gating pattern used inuseTriggerDeliveriesto returnfalsewhen the query is disabled:Suggested fix
return { subscriptions, count: query.data?.count ?? 0, - isLoading: query.isPending, + isLoading: connectionId ? query.isPending : false, error: query.error, } }web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerConnections.ts-60-64 (1)
60-64:⚠️ Potential issue | 🟡 MinorGate loading state when
integrationKeyis empty.Line 63 can surface a loading state even when the query is intentionally disabled (line 48). Mirror the
useTriggerDeliverieshook pattern and gateisLoadingby the input key.Suggested fix
export const useTriggerIntegrationConnections = (integrationKey: string) => { const query = useAtomValue(triggerIntegrationConnectionsAtomFamily(integrationKey)) const connections = useMemo<TriggerConnection[]>( () => query.data?.connections ?? [], [query.data?.connections], ) return { connections, count: query.data?.count ?? 0, - isLoading: query.isPending, + isLoading: integrationKey ? query.isPending : false, error: query.error, } }web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.ts-32-35 (1)
32-35:⚠️ Potential issue | 🟡 MinorMake
isLoadingconditional on both required keys.Line 34 should be gated by
integrationKeyandeventKeyso consumers don't get a loading signal when the query is disabled due to missing prerequisites.Suggested fix
return { event: query.data?.event ?? null, - isLoading: query.isPending, + isLoading: integrationKey && eventKey ? query.isPending : false, error: query.error, } }web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx-248-248 (1)
248-248:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid non-unique
rowKeyfallback.Line 248 can return
"", which can produce duplicate keys and unstable row identity when multiple records miss these fields.Suggested fix
- rowKey={(record) => record.id ?? record.slug ?? record.data?.event_key ?? ""} + rowKey={(record) => + record.id ?? + record.slug ?? + `${record.connection_id ?? "unknown"}:${record.data?.event_key ?? "unknown"}:${record.created_at ?? "unknown"}` + }web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerSubscriptionDrawer.tsx-97-100 (1)
97-100:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEdit prefill uses raw workflow revision ID as UI label
Line 99 sets
workflowLabelto the revision ID, so edit mode can display an opaque ID instead of the workflow display name/version format used elsewhere.As per coding guidelines, display workflow names via
workflowMolecule.selectors.artifactName(entityId)and use the prescribed label rules for workflow/variant naming.Source: Coding guidelines
web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerDeliveriesDrawer.tsx-51-55 (1)
51-55:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winStatus tag color ignores the status-code fallback
Line 51 already falls back to
status.code, but Line 54 colors usingstatus.typeonly. Code-only statuses can display the wrong semantic color.Suggested fix
-const type = record.status?.type ?? record.status?.code +const type = record.status?.type ?? record.status?.code return ( <Tooltip title={record.status?.message ?? undefined}> - <Tag color={statusColor(record.status?.type)}>{type ?? "unknown"}</Tag> + <Tag color={statusColor(type)}>{type ?? "unknown"}</Tag> </Tooltip> )web/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsx-56-57 (1)
56-57:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winName column can render blank for valid rows
Line 56 falls back to
slug, but if bothnameandslugare absent the cell is empty. Addintegration_keyas final fallback so every row remains identifiable.Suggested fix
-<Typography.Text>{record.name || record.slug}</Typography.Text> +<Typography.Text>{record.name || record.slug || record.integration_key}</Typography.Text>web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerSubscriptionDrawer.tsx-138-146 (1)
138-146:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReject non-object JSON for
inputs_fieldsLine 140 accepts any valid JSON (including arrays/primitives), but
inputs_fieldsis expected to be an object map. This can produce contract failures downstream.Suggested fix
let inputsFields: Record<string, unknown> = {} try { - inputsFields = inputsText.trim() ? JSON.parse(inputsText) : {} + const parsed = inputsText.trim() ? JSON.parse(inputsText) : {} + if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") { + throw new Error("Inputs mapping must be a JSON object") + } + inputsFields = parsed as Record<string, unknown> setInputsError(null) } catch { setInputsError("Invalid JSON") message.error("inputs mapping is not valid JSON") return }api/oss/src/core/gateway/connections/dtos.py-112-118 (1)
112-118:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse enum typing for
ConnectionRequest.auth_schemeto keep validation consistent.
ConnectionCreateData.auth_schemeis enum-validated, butConnectionRequest.auth_schemeis a plainstr, which lets invalid schemes bypass early validation and fail later in adapter calls.🔧 Proposed fix
class ConnectionRequest(BaseModel): @@ - auth_scheme: Optional[str] = None + auth_scheme: Optional[ConnectionAuthScheme] = Noneapi/oss/src/apis/fastapi/triggers/router.py-376-377 (1)
376-377:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winValidate
limitat the API boundary.Line 376 allows invalid values (e.g.,
0or negative), which can propagate upstream and surface as adapter failures instead of a clean client validation error.Suggested fix
- limit: Optional[int] = Query(default=None), + limit: Optional[int] = Query(default=None, ge=1, le=1000),docs/designs/gateway-triggers/wp/WP4-specs.md-20-25 (1)
20-25:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate WP4 ACK semantics from 200 to 202.
Line 22 and Line 54 document
200for no-op/unset-secret, but this PR’s ingress contract is202 Accepted. Keeping200here creates avoidable contract drift.Suggested doc fix
-- HMAC-SHA256 verify over `{id}.{ts}.{body}` with `COMPOSIO_WEBHOOK_SECRET`; 401 bad sig; - 200 no-op when secret unset; add `COMPOSIO_WEBHOOK_SECRET` to `env`. +- HMAC-SHA256 verify over `{id}.{ts}.{body}` with `COMPOSIO_WEBHOOK_SECRET`; 401 bad sig; + 202 no-op when secret unset; add `COMPOSIO_WEBHOOK_SECRET` to `env`. @@ -- Forged signature → 401; unset secret → 200 no-op. +- Forged signature → 401; unset secret → 202 no-op.Also applies to: 54-55
docs/designs/gateway-triggers/wp/WP4-status.md-7-25 (1)
7-25:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRefresh WP4 status to match implemented work.
The document still marks WP4 as
NOT STARTED, but this stack includes ingress, dispatch runtime, worker wiring, and tests. This stale status will confuse follow-on planning.docs/designs/gateway-triggers/wp/WP0-status.md-15-18 (1)
15-18:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlign the connection path spelling.
This status file still uses the flat
core/connections/dbs/postgres/connectionsnames, but the spec and implementation cohort use thegateway/subpaths. Keeping both spellings will send the next lane to the wrong package.Based on the stack context, this PR already uses
core/gateway/connections/anddbs/postgres/gateway/connections/.🛠️ Suggested alignment
- [x] `core/connections/` service + DAO interface + `ConnectionsService` + `ConnectionsGatewayInterface` + [x] `core/gateway/connections/` service + DAO interface + `ConnectionsService` + `ConnectionsGatewayInterface` - [x] `dbs/postgres/connections/` DBE + DAO + mappings + [x] `dbs/postgres/gateway/connections/` DBE + DAO + mappingsAlso applies to: 50-59
docs/designs/gateway-triggers/wp/WL-runbook.md-128-154 (1)
128-154:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLabel the markdown example fence.
The unlabeled fence trips MD040;
mdis sufficient here.As per the markdownlint hint, the docs PR body example needs a language tag.
🛠️ Proposed fix
-``` +```mdSource: Linters/SAST tools
docs/designs/gateway-triggers/wp/WP1-status.md-41-42 (1)
41-42:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPermission note conflicts with the WP1 frozen contract.
This says no
VIEW_TRIGGERSand reusesVIEW_TOOLS, but WP1 specs freeze the opposite rule. Please reconcile this to avoid implementer confusion on authorization behavior.docs/designs/gateway-triggers/wp/WP2-specs.md-29-33 (1)
29-33:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFrozen resolver return type is too narrow.
The spec locks
-> dict, but current trigger dispatch logic already handles scalar/non-dict results fromresolve_target_fields. Please widen the contract wording to avoid future mismatches.
🧹 Nitpick comments (9)
web/packages/agenta-entities/src/gatewayTrigger/api/client.ts (1)
5-19: ⚡ Quick winTrim block comments to “why”-only notes.
These docblocks narrate implementation details (“what”) across many lines; please reduce to terse invariant-level comments only, or rely on naming/extraction.
As per coding guidelines, “Keep AI-generated in-code comments minimal; comment only the non-obvious why, never the what.”
Source: Coding guidelines
web/packages/agenta-entities/src/gatewayTrigger/api/api.ts (1)
1-12: ⚡ Quick winReduce explanatory prose comments in this module.
The section/header comments mostly restate behavior; please keep only short “why” notes where needed.
As per coding guidelines, comments should be minimal and explain non-obvious rationale, not implementation narration.
Also applies to: 42-42, 111-111, 133-133, 243-243
Source: Coding guidelines
web/packages/agenta-entities/tests/unit/gatewayTriggerApi.test.ts (1)
1-14: ⚡ Quick winShorten the top-of-file comment block.
Please keep only the non-obvious invariant/intent and drop step-by-step prose.
As per coding guidelines, comments should be terse and explain “why,” not narrate “what.”
Source: Coding guidelines
web/packages/agenta-entity-ui/src/gatewayTrigger/index.ts (1)
1-8: ⚡ Quick winCondense the module docblock to a one-liner (or remove).
Current block explains behavior in detail; this can be inferred from exports and naming.
As per coding guidelines, keep in-code comments minimal and focused on subtle “why” context only.
Source: Coding guidelines
web/packages/agenta-entities/src/gatewayTrigger/core/types.ts (1)
1-15: ⚡ Quick winTrim non-obvious-comment blocks to terse intent notes only.
These large narrative blocks describe what and migration context; please reduce to minimal “why” comments (or rely on symbol names/module docs) to match repo standards.
As per coding guidelines, “Keep AI-generated in-code comments minimal; comment only the non-obvious why … never the what.”
Also applies to: 90-95, 134-140, 235-239
Source: Coding guidelines
web/packages/agenta-entities/src/gatewayTrigger/hooks/useCatalogEvents.ts (1)
14-16: ⚡ Quick winUnify catalog-search atom ownership to avoid split state.
eventsSearchAtomhere overlaps conceptually witheventSearchAtominstate/atoms.ts; keeping two similarly named atoms for the same concern invites accidental desync across consumers. Prefer exporting one shared atom from a single module and reusing it here.api/oss/tests/pytest/acceptance/triggers/test_triggers_ingress.py (1)
151-163: ⚡ Quick winMake the dedup assertion deterministic and cleanup-safe.
Line 151 currently allows a false positive (
<= 1) when the async dispatcher hasn’t written any row yet. Poll for completion (bounded) and then assert exactly one delivery; also run cleanup infinallywith response checks.Proposed test hardening
+from time import sleep @@ - # The dispatch is async; the dedup guard means at most one delivery row - # exists for this (subscription, event_id). - deliveries = authed_api( - "POST", - "/triggers/deliveries/query", - json={ - "delivery": {"subscription_id": subscription_id, "event_id": event_id} - }, - ).json()["deliveries"] - assert len(deliveries) <= 1 - - authed_api("DELETE", f"/triggers/subscriptions/{subscription_id}") - authed_api("DELETE", f"/tools/connections/{connection_id}") + try: + deliveries = [] + for _ in range(20): + query = authed_api( + "POST", + "/triggers/deliveries/query", + json={ + "delivery": {"subscription_id": subscription_id, "event_id": event_id} + }, + ) + assert query.status_code == 200, query.text + deliveries = query.json()["deliveries"] + if deliveries: + break + sleep(0.25) + + assert len(deliveries) == 1 + finally: + delete_sub = authed_api("DELETE", f"/triggers/subscriptions/{subscription_id}") + assert delete_sub.status_code in (200, 204), delete_sub.text + delete_conn = authed_api("DELETE", f"/tools/connections/{connection_id}") + assert delete_conn.status_code in (200, 204), delete_conn.textapi/oss/tests/pytest/unit/triggers/test_triggers_dispatcher.py (1)
129-149: ⚡ Quick winAdd coverage for the raised-exception branch in
dispatch.Current tests validate non-200 responses, but not the
invoke_workflowexception path that should write a failed delivery and re-raise.Suggested additional unit test
+import pytest @@ async def test_workflow_non_200_writes_failed_delivery(): @@ assert delivery.status.code == "500" + + +async def test_workflow_exception_writes_failed_delivery_and_reraises(): + project_id = uuid4() + reference = Reference(slug="wf-1") + subscription = _make_subscription(references={"workflow": reference}) + dao = _make_dao(resolved=(project_id, subscription)) + + workflows = MagicMock() + workflows.invoke_workflow = AsyncMock(side_effect=RuntimeError("boom")) + dispatcher = TriggersDispatcher(triggers_dao=dao, workflows_service=workflows) + + with pytest.raises(RuntimeError, match="boom"): + await dispatcher.dispatch(trigger_id="ti_1", event_id="e1", event=_EVENT) + + dao.write_delivery.assert_awaited_once() + delivery = dao.write_delivery.await_args.kwargs["delivery"] + assert delivery.status.code == "500"api/oss/src/tasks/asyncio/triggers/dispatcher.py (1)
1-9: ⚡ Quick winTrim narrative comments to “why-only” comments.
Several comments/docstrings here narrate behavior rather than a non-obvious invariant. Please reduce these to terse “why” notes (or rely on naming/extraction).
As per coding guidelines, "Keep AI-generated in-code comments minimal; comment only the non-obvious why ... never the what."
Also applies to: 109-130
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: bb4b1c2d-a35f-4d60-ab97-8083ce9d0008
📒 Files selected for processing (125)
AGENTS.mdapi/ee/src/core/access/permissions/types.pyapi/ee/tests/pytest/acceptance/tools/__init__.pyapi/ee/tests/pytest/acceptance/tools/test_tools_connections.pyapi/ee/tests/pytest/acceptance/triggers/__init__.pyapi/ee/tests/pytest/acceptance/triggers/test_triggers_catalog.pyapi/ee/tests/pytest/acceptance/triggers/test_triggers_subscriptions.pyapi/entrypoints/routers.pyapi/entrypoints/worker_triggers.pyapi/oss/databases/postgres/migrations/core_oss/versions/oss000000002_rename_tool_connections_to_gateway_connections.pyapi/oss/databases/postgres/migrations/core_oss/versions/oss000000003_add_trigger_subscriptions_and_deliveries.pyapi/oss/src/apis/fastapi/tools/models.pyapi/oss/src/apis/fastapi/tools/router.pyapi/oss/src/apis/fastapi/triggers/__init__.pyapi/oss/src/apis/fastapi/triggers/models.pyapi/oss/src/apis/fastapi/triggers/router.pyapi/oss/src/core/gateway/__init__.pyapi/oss/src/core/gateway/connections/__init__.pyapi/oss/src/core/gateway/connections/dtos.pyapi/oss/src/core/gateway/connections/exceptions.pyapi/oss/src/core/gateway/connections/interfaces.pyapi/oss/src/core/gateway/connections/providers/__init__.pyapi/oss/src/core/gateway/connections/providers/composio/__init__.pyapi/oss/src/core/gateway/connections/providers/composio/adapter.pyapi/oss/src/core/gateway/connections/registry.pyapi/oss/src/core/gateway/connections/service.pyapi/oss/src/core/gateway/connections/utils.pyapi/oss/src/core/tools/dtos.pyapi/oss/src/core/tools/interfaces.pyapi/oss/src/core/tools/providers/composio/adapter.pyapi/oss/src/core/tools/service.pyapi/oss/src/core/triggers/__init__.pyapi/oss/src/core/triggers/dtos.pyapi/oss/src/core/triggers/exceptions.pyapi/oss/src/core/triggers/interfaces.pyapi/oss/src/core/triggers/providers/__init__.pyapi/oss/src/core/triggers/providers/composio/__init__.pyapi/oss/src/core/triggers/providers/composio/adapter.pyapi/oss/src/core/triggers/providers/composio/catalog.pyapi/oss/src/core/triggers/registry.pyapi/oss/src/core/triggers/service.pyapi/oss/src/core/webhooks/delivery.pyapi/oss/src/dbs/postgres/gateway/__init__.pyapi/oss/src/dbs/postgres/gateway/connections/__init__.pyapi/oss/src/dbs/postgres/gateway/connections/dao.pyapi/oss/src/dbs/postgres/gateway/connections/dbes.pyapi/oss/src/dbs/postgres/gateway/connections/mappings.pyapi/oss/src/dbs/postgres/triggers/__init__.pyapi/oss/src/dbs/postgres/triggers/dao.pyapi/oss/src/dbs/postgres/triggers/dbas.pyapi/oss/src/dbs/postgres/triggers/dbes.pyapi/oss/src/dbs/postgres/triggers/mappings.pyapi/oss/src/middlewares/auth.pyapi/oss/src/tasks/asyncio/triggers/__init__.pyapi/oss/src/tasks/asyncio/triggers/dispatcher.pyapi/oss/src/tasks/taskiq/triggers/__init__.pyapi/oss/src/tasks/taskiq/triggers/worker.pyapi/oss/src/utils/env.pyapi/oss/tests/pytest/acceptance/tools/test_tools_connections.pyapi/oss/tests/pytest/acceptance/triggers/__init__.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_catalog.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_ingress.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_subscriptions.pyapi/oss/tests/pytest/unit/models/test_lifecycle_conventions.pyapi/oss/tests/pytest/unit/triggers/__init__.pyapi/oss/tests/pytest/unit/triggers/test_triggers_dispatcher.pyapi/oss/tests/pytest/unit/triggers/test_triggers_signature.pyapi/oss/tests/pytest/unit/webhooks/test_webhooks_tasks.pydocs/designs/gateway-triggers/gap.mddocs/designs/gateway-triggers/mapping.mddocs/designs/gateway-triggers/mimics.mddocs/designs/gateway-triggers/plan.mddocs/designs/gateway-triggers/proposal.mddocs/designs/gateway-triggers/research.mddocs/designs/gateway-triggers/wp/WL-runbook.mddocs/designs/gateway-triggers/wp/WP0-specs.mddocs/designs/gateway-triggers/wp/WP0-status.mddocs/designs/gateway-triggers/wp/WP1-specs.mddocs/designs/gateway-triggers/wp/WP1-status.mddocs/designs/gateway-triggers/wp/WP2-specs.mddocs/designs/gateway-triggers/wp/WP2-status.mddocs/designs/gateway-triggers/wp/WP3-specs.mddocs/designs/gateway-triggers/wp/WP3-status.mddocs/designs/gateway-triggers/wp/WP4-specs.mddocs/designs/gateway-triggers/wp/WP4-status.mddocs/designs/gateway-triggers/wp/WP5-specs.mddocs/designs/gateway-triggers/wp/WP5-status.mddocs/designs/gateway-triggers/wp/WP6-specs.mddocs/designs/gateway-triggers/wp/WP6-status.mdhosting/docker-compose/ee/docker-compose.dev.ymlhosting/docker-compose/ee/docker-compose.gh.local.ymlhosting/docker-compose/ee/docker-compose.gh.ymlhosting/docker-compose/oss/docker-compose.dev.ymlhosting/docker-compose/oss/docker-compose.gh.local.ymlhosting/docker-compose/oss/docker-compose.gh.ssl.ymlhosting/docker-compose/oss/docker-compose.gh.ymlsdks/python/agenta/sdk/utils/resolvers.pysdks/python/oss/tests/pytest/unit/test_resolvers.pyweb/oss/src/components/Sidebar/SettingsSidebar.tsxweb/oss/src/components/pages/settings/Triggers/Triggers.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsxweb/oss/src/pages/w/[workspace_id]/p/[project_id]/settings/index.tsxweb/packages/agenta-entities/package.jsonweb/packages/agenta-entities/src/gatewayTrigger/api/api.tsweb/packages/agenta-entities/src/gatewayTrigger/api/client.tsweb/packages/agenta-entities/src/gatewayTrigger/api/index.tsweb/packages/agenta-entities/src/gatewayTrigger/core/index.tsweb/packages/agenta-entities/src/gatewayTrigger/core/types.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/index.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useCatalogEvents.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerConnections.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerDeliveries.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscription.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscriptions.tsweb/packages/agenta-entities/src/gatewayTrigger/index.tsweb/packages/agenta-entities/src/gatewayTrigger/state/atoms.tsweb/packages/agenta-entities/src/gatewayTrigger/state/index.tsweb/packages/agenta-entities/tests/unit/gatewayTriggerApi.test.tsweb/packages/agenta-entity-ui/package.jsonweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerDeliveriesDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerEventsDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerSubscriptionDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/index.ts
💤 Files with no reviewable changes (1)
- api/oss/src/core/tools/dtos.py
Normalize the inbound provider envelope in the dispatcher into a stable context (event.attributes + synthetic trigger_id/trigger_type/timestamp/ created_at), parallel to webhooks' event context. Resolve and complete the bound workflow reference on subscription create/edit (the /deploy pattern) so a variant id is resolved to a runnable revision. Align the drawer's mapping suggestions + live preview to the same normalized shape. Update trigger tests to the new shape and always-verify ingress; gate the create-roundtrip acceptance tests on an ACTIVE connected account. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
web/packages/agenta-entity-ui/src/gatewayTool/drawers/ConnectDrawer.tsx (1)
80-90:⚠️ Potential issue | 🔴 CriticalDrawer mutation flow misses trigger cache invalidation.
After
createToolConnection, both mutation paths (lines 90 and 119) callinvalidateConnections()which only invalidates tool caches. Because connections are shared between tools and triggers (stored in the samegateway_connectionsrows), the trigger connection queries remain stale. Compare withTriggerConnectDrawer, which correctly invalidates both surfaces.Suggested fix
const invalidateConnections = useCallback(() => { queryClient.invalidateQueries({queryKey: ["tools", "connections"]}) queryClient.invalidateQueries({queryKey: ["tools", "catalog"]}) + queryClient.invalidateQueries({queryKey: ["triggers", "connections"]}) }, [])web/oss/src/components/pages/settings/Tools/hooks/useToolsConnections.ts (1)
22-35:⚠️ Potential issue | 🟠 MajorInvalidate trigger connection cache on tool-side mutations.
The
invalidate()callback (lines 22–35) and its use inhandleCreate(line 56–58),handleDelete, andhandleRefreshonly invalidate["tools", ...]keys. Since tools and triggers share the samegateway_connectionsrows, trigger screens can display stale connection state after these mutations until an unrelated refetch occurs. Other files in the codebase (useToolConnectionActions.ts,useTriggerConnectionActions.ts) correctly invalidate both surfaces.Suggested fix
const invalidate = useCallback(() => { queryClient.invalidateQueries({ queryKey: ["tools", "connections"], }) + queryClient.invalidateQueries({ + queryKey: ["triggers", "connections"], + }) }, [integrationKey])web/oss/src/styles/globals.css (1)
384-443:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
webhooks-tableCSS rules appear disconnected from the rendered table.These selectors only apply when the table (or an ancestor) has
webhooks-table, but the providedWebhooks.tsxtable markup doesn’t set that class. The column-width rules likely won’t take effect.Suggested fix outside this file
- <Table + <Table + className="webhooks-table" columns={columns} dataSource={webhooks ?? []} loading={isLoading} rowKey="id"api/oss/src/apis/fastapi/triggers/router.py (1)
111-117:⚠️ Potential issue | 🟠 MajorSupport both
/composio/eventsand/composio/events/to prevent POST redirect failures.FastAPI's default
redirect_slashes=Trueissues HTTP 307 redirects when requests don't match the defined route's trailing-slash format. For POST webhook ingestion, some providers do not properly resend the request body after a 307 redirect, causing lost events.Suggested fix
self.router.add_api_route( "/composio/events/", self.ingest_composio_event, methods=["POST"], operation_id="ingest_composio_event", response_model=TriggerEventAck, status_code=status.HTTP_202_ACCEPTED, ) + self.router.add_api_route( + "/composio/events", + self.ingest_composio_event, + methods=["POST"], + include_in_schema=False, + )
🧹 Nitpick comments (9)
api/ee/tests/pytest/acceptance/triggers/test_triggers_connections.py (1)
29-33: ⚡ Quick winUse the shared API env object instead of
os.getenvhere.This file introduces direct env access at Line 29; in API code/tests we should read config from the shared
envobject to keep config handling centralized.Suggested change
-import os +from oss.src.utils.env import env ... -_COMPOSIO_ENABLED = bool(os.getenv("COMPOSIO_API_KEY")) +_COMPOSIO_ENABLED = bool(env.composio.api_key)As per coding guidelines:
api/**/*.py: “Add new API environment variables toapi/oss/src/utils/env.pyand consume them via the sharedenvobject instead of callingos.getenv(...)directly”.Source: Coding guidelines
web/oss/src/components/Webhooks/WebhookDrawer.tsx (1)
58-67: ⚡ Quick winReuse a shared provider-detection helper instead of duplicating URL heuristics.
This edit path derives provider with inline hostname checks; the same concern is already handled elsewhere via shared helper logic. Consolidating avoids drift between pages.
hosting/docker-compose/ee/docker-compose.gh.local.yml (1)
516-545: 💤 Low valueConsider caching pip packages for faster container restarts.
The
composioservice runspip installon every container start, which adds latency on restarts. For a dev-only tunnel service withrestart: always, this is acceptable but could be optimized by building a custom image or using a volume-cached pip directory.api/oss/src/core/triggers/utils.py (1)
45-68: 💤 Low valueConsider returning cached value on Composio failure instead of None.
When
force_refresh=Falseand the cache is empty, if Composio fails (line 57-59), the method returnsNone. However, if there's a stale/expired cache entry that was evicted, users get no secret. Consider a fallback strategy or ensure callers handleNonegracefully.web/oss/src/services/webhooks/types.ts (1)
22-43: ⚡ Quick winSplit API payload types from draft form-state types.
WebhookFormValuescurrently models payload-style fields (event_types), while form consumers use draft fields (events,header_list), which is why downstream code needsas any. Define a dedicated draft type and use it in form-preview helpers to restore type safety.Proposed direction
interface WebhookFormValuesBase<P extends WebhookProvider = WebhookProvider> { provider: P name?: string event_types?: WebhookEventType[] } +export interface WebhookDraftFields { + events?: WebhookEventType[] + header_list?: {key: string; value: string}[] +} + export interface WebhookConfigFormValues extends WebhookFormValuesBase<"webhook"> { url?: string headers?: Record<string, string> auth_mode?: "signature" | "authorization" auth_value?: string } export interface GitHubFormValues extends WebhookFormValuesBase<"github"> { github_sub_type?: GitHubDispatchType github_repo?: string github_pat?: string github_workflow?: string github_branch?: string } export type WebhookFormValues = WebhookConfigFormValues | GitHubFormValues +export type WebhookDraftFormValues = + | (WebhookConfigFormValues & WebhookDraftFields) + | (GitHubFormValues & WebhookDraftFields)Then type form-preview paths with
WebhookDraftFormValuesinstead ofWebhookFormValues.web/packages/agenta-entities/src/gatewayTrigger/api/client.ts (1)
30-36: ⚡ Quick winTrim this block comment to a one-line “why” note (or remove it).
The current comment narrates behavior that is already clear from the function body, which adds maintenance noise.
As per coding guidelines, “Keep AI-generated in-code comments minimal; comment only the non-obvious why … never the what.”
Source: Coding guidelines
api/oss/src/apis/fastapi/triggers/router.py (1)
136-159: 🏗️ Heavy liftUse
POST /queryfor searchable catalog list endpoints.The new searchable/filterable catalog lists are exposed via GET query params; the router guideline requires query-style filtering via
POST /querypayload endpoints.As per coding guidelines, “
api/oss/src/apis/fastapi/**/router.py: UsePOST /queryfor filtering/search with payload support.”Also applies to: 603-614, 727-736
Source: Coding guidelines
api/entrypoints/dispatcher_composio.py (1)
70-70: 💤 Low valuehttpx.Client is never closed.
The
forwardclient is created but never explicitly closed. Since this script runs indefinitely, it's not a functional issue, but for resource hygiene consider using a context manager or callingforward.close()on exit. However, given this is a dev-only script that blocks forever onwait_forever(), this is acceptable as-is.web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerCatalogDrawer.tsx (1)
426-444: 💤 Low valueEvent cards are hoverable but not actionable.
The event
Cardhashoverableprop but noonClickhandler. If clicking an event should do something (e.g., show event details or select it for subscription), add the handler. If cards are purely informational, consider removinghoverableto avoid misleading UX affordance.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2c857f25-e2d7-4af9-b561-b325285e1c10
📒 Files selected for processing (116)
api/ee/tests/pytest/acceptance/triggers/test_triggers_connections.pyapi/ee/tests/pytest/acceptance/triggers/test_triggers_subscriptions.pyapi/entrypoints/dispatcher_composio.pyapi/entrypoints/routers.pyapi/oss/src/apis/fastapi/tools/router.pyapi/oss/src/apis/fastapi/triggers/models.pyapi/oss/src/apis/fastapi/triggers/router.pyapi/oss/src/core/gateway/catalog/__init__.pyapi/oss/src/core/gateway/catalog/dtos.pyapi/oss/src/core/gateway/catalog/interfaces.pyapi/oss/src/core/gateway/catalog/providers/__init__.pyapi/oss/src/core/gateway/catalog/providers/composio/__init__.pyapi/oss/src/core/gateway/catalog/providers/composio/adapter.pyapi/oss/src/core/gateway/catalog/registry.pyapi/oss/src/core/gateway/catalog/service.pyapi/oss/src/core/gateway/connections/providers/composio/adapter.pyapi/oss/src/core/gateway/providers/__init__.pyapi/oss/src/core/gateway/providers/composio/__init__.pyapi/oss/src/core/gateway/providers/composio/errors.pyapi/oss/src/core/tools/dtos.pyapi/oss/src/core/tools/providers/composio/adapter.pyapi/oss/src/core/tools/service.pyapi/oss/src/core/triggers/dtos.pyapi/oss/src/core/triggers/interfaces.pyapi/oss/src/core/triggers/providers/composio/adapter.pyapi/oss/src/core/triggers/service.pyapi/oss/src/core/triggers/utils.pyapi/oss/src/middlewares/auth.pyapi/oss/src/tasks/asyncio/triggers/dispatcher.pyapi/oss/src/utils/env.pyapi/oss/tests/manual/triggers/try_composio_triggers.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_connections.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_ingress.pyapi/oss/tests/pytest/acceptance/triggers/test_triggers_subscriptions.pyapi/oss/tests/pytest/unit/triggers/test_triggers_dispatcher.pyapi/oss/tests/pytest/unit/triggers/test_triggers_signature.pyhosting/docker-compose/ee/docker-compose.dev.ymlhosting/docker-compose/ee/docker-compose.gh.local.ymlhosting/docker-compose/ee/docker-compose.gh.ymlhosting/docker-compose/oss/docker-compose.dev.ymlhosting/docker-compose/oss/docker-compose.gh.local.ymlhosting/docker-compose/oss/docker-compose.gh.ssl.ymlhosting/docker-compose/oss/docker-compose.gh.ymlhosting/docker-compose/run.shweb/_reference/agenta-sdk/src/types.tsweb/oss/src/components/DrillInView/OSSdrillInUIProvider.tsxweb/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/GatewayToolsPanel.tsxweb/oss/src/components/Sidebar/SettingsSidebar.tsxweb/oss/src/components/Webhooks/Modals/DeleteWebhookModal.tsxweb/oss/src/components/Webhooks/Modals/SecretRevealModal.tsxweb/oss/src/components/Webhooks/RequestPreview.tsxweb/oss/src/components/Webhooks/WebhookDrawer.tsxweb/oss/src/components/Webhooks/WebhookFieldRenderer.tsxweb/oss/src/components/Webhooks/WebhookLogsTab.tsxweb/oss/src/components/Webhooks/assets/constants.tsweb/oss/src/components/Webhooks/assets/types.tsweb/oss/src/components/Webhooks/utils/buildPreviewRequest.tsweb/oss/src/components/Webhooks/utils/buildSubscription.tsweb/oss/src/components/Webhooks/utils/handleTestResult.tsweb/oss/src/components/Webhooks/widgets/AdvanceConfigWidget.tsxweb/oss/src/components/Webhooks/widgets/DispatchAlertWidget.tsxweb/oss/src/components/Webhooks/widgets/HeaderListWidget.tsxweb/oss/src/components/pages/settings/APIKeys/APIKeys.tsxweb/oss/src/components/pages/settings/Tools/components/GatewayToolsSection.tsxweb/oss/src/components/pages/settings/Tools/hooks/useIntegrationDetail.tsweb/oss/src/components/pages/settings/Tools/hooks/useToolsConnections.tsweb/oss/src/components/pages/settings/Tools/hooks/useToolsIntegrations.tsweb/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsxweb/oss/src/components/pages/settings/Webhooks/Webhooks.tsxweb/oss/src/pages/w/[workspace_id]/p/[project_id]/settings/index.tsxweb/oss/src/services/webhooks/api.tsweb/oss/src/services/webhooks/types.tsweb/oss/src/state/automations/state.tsweb/oss/src/state/webhooks/atoms.tsweb/oss/src/state/webhooks/state.tsweb/oss/src/styles/globals.cssweb/packages/agenta-entities/src/gatewayTool/api/api.tsweb/packages/agenta-entities/src/gatewayTool/api/index.tsweb/packages/agenta-entities/src/gatewayTool/hooks/index.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolActionDetail.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolCatalogActions.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolCatalogIntegrations.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolConnectionActions.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolConnectionQuery.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolConnectionsQuery.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationConnections.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationDetail.tsweb/packages/agenta-entities/src/gatewayTool/index.tsweb/packages/agenta-entities/src/gatewayTool/state/atoms.tsweb/packages/agenta-entities/src/gatewayTool/state/index.tsweb/packages/agenta-entities/src/gatewayTrigger/api/api.tsweb/packages/agenta-entities/src/gatewayTrigger/api/client.tsweb/packages/agenta-entities/src/gatewayTrigger/api/index.tsweb/packages/agenta-entities/src/gatewayTrigger/core/types.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/index.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerCatalogEvents.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerCatalogIntegrations.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerConnectionActions.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.tsweb/packages/agenta-entities/src/gatewayTrigger/index.tsweb/packages/agenta-entities/src/gatewayTrigger/state/atoms.tsweb/packages/agenta-entities/src/gatewayTrigger/state/index.tsweb/packages/agenta-entities/src/index.tsweb/packages/agenta-entity-ui/src/gatewayTool/components/SchemaForm.tsxweb/packages/agenta-entity-ui/src/gatewayTool/drawers/CatalogDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTool/drawers/ConnectDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTool/drawers/ConnectionManagerDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTool/drawers/ToolExecutionDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerCatalogDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerConnectDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerDeliveriesDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerEventsDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerSubscriptionDrawer.tsxweb/packages/agenta-entity-ui/src/gatewayTrigger/index.tsweb/tests/tests/fixtures/base.fixture/providerHelpers/index.ts
💤 Files with no reviewable changes (1)
- web/oss/src/state/automations/state.ts
✅ Files skipped from review due to trivial changes (5)
- api/oss/src/core/gateway/catalog/providers/composio/init.py
- web/packages/agenta-entity-ui/src/gatewayTool/components/SchemaForm.tsx
- web/_reference/agenta-sdk/src/types.ts
- web/packages/agenta-entities/src/index.ts
- web/oss/src/components/pages/settings/APIKeys/APIKeys.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
- api/oss/src/middlewares/auth.py
- web/packages/agenta-entities/src/gatewayTrigger/state/atoms.ts
- web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerDeliveriesDrawer.tsx
- api/oss/src/core/triggers/interfaces.py
- web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.ts
- web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
- api/oss/tests/pytest/acceptance/triggers/test_triggers_subscriptions.py
- api/entrypoints/routers.py
- api/ee/tests/pytest/acceptance/triggers/test_triggers_subscriptions.py
- web/oss/src/components/Sidebar/SettingsSidebar.tsx
- api/oss/src/tasks/asyncio/triggers/dispatcher.py
Resolve the bound reference via the canonical WorkflowsService.retrieve_workflow_revision (handles application/evaluator/ workflow + environment families) and rebuild the completed family with build_retrieval_info, so invoke_workflow finds the service uri. Raise TriggerReferenceInvalid when it cannot resolve. Skip soft-deleted subscriptions in the ti_* resolver. FE: scope the picker to application workflows and send the reference family by its true kind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ggers, schedules, webhooks
Add trigger schedules: a cron-expression analogue to trigger subscriptions that
fires the same dispatch path on each matching minute tick. Promote ti_id to a
top-level indexed column, type subscription/schedule/webhook flags, and add
/start and /stop play/pause routes (is_active) across all three domains, with
is_valid reserved for third-party connection sync on subscriptions.
- core/triggers: TriggerSchedule* DTOs, schedule CRUD + croniter validation,
refresh_schedules fire gate, entity-agnostic dispatcher
- db: fold schedules + ti_id column + generalized deliveries into oss000000003;
data-only oss000000004 backfills webhook flags.is_active
- cron: crons/triggers.{sh,txt} wired into oss+ee compose and all oss/ee x dev/gh Dockerfiles
- web: schedule drawer + list section, ActiveToggle, schedule atoms/hooks
- tests: schedule + webhook play/pause acceptance, dispatcher is_valid unit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nature to hex The dispatcher refactor added a required triggers_dao kwarg to TriggersWorker but worker_triggers.py was missed, crash-looping the worker (ingress 202 but nothing drained the queue). Live events also confirmed Composio signs lowercase hex, so the diagnostic dual hex/base64 accept is collapsed to hex-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…39 os.getenv Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…grade, F37 skip, F40 dup-flags - F5: make triggers_dao/connections_service/workflows_service required (drop Optional) - F16: extract selector-preview to pure core/selectorPreview.ts + 12 web unit tests - F17: add schedule cron fire-gate unit tests (_validate_schedule + refresh_schedules) - F18: try/finally cleanup in six connection/subscription lifecycle tests - F20: symmetric oss000000004 downgrade (strip only backfill-created flags) - F37: skip ingress dedup test when webhook secret unresolvable (no 401 flake) - F40: remove duplicate flags key breaking @agenta/entities tsc build - F2 verified not fail-open; F13/F24/F39 dispositioned wontfix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the generated Python (agenta_client/triggers/*, trigger_* types) and TypeScript (agenta-api-client triggers resource, Trigger*/Connection*/Catalog* types) clients for the gateway-triggers surface; renames the tool-scoped connection/provider/auth enums to the shared Connection*/Catalog* names and drops the obsolete Tool* variants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rsion global-setup now reads the backend's /auth/discover response and promotes the 'auto' auth mode to the method the deployment actually serves: 'email:password' (local dev / no SMTP — direct signup, no email verification) vs 'email:otp' (SMTP/SendGrid enabled). Local dev no longer demands Testmail/OTP wiring. run-tests excludes the agenta-web-tests workspace from the vitest layer pass so its test:acceptance script doesn't recurse into the playwright runner. Re-enable multi-worker by dropping the temporary workers:1 pin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gateway-triggers feature runs a dedicated queue consumer (entrypoints.worker_triggers) alongside the schedule-ticking cron. It was wired into every docker-compose variant but missing from the Helm chart, so Helm-deployed clusters ingest+enqueue trigger events (and tick schedules via the baked crontab) but have nothing consuming the queue — no delivery ever fires. This is the F38 worker-wiring gap, permanent on Kubernetes. Adds worker-triggers-deployment.yaml modeled on worker-webhooks (same image, commonEnv, init containers, NewRelic command branch, pgrep liveness probe), the agenta.workerTriggers.enabled/.replicas helpers, and the workerTriggers knob in both values examples. Defaults enabled with replicas:1; toggle off via workerTriggers.enabled=false. Verified with helm template + helm lint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…* names The fern regen renamed the shared gateway enums/DTOs from Tool*-prefixed to the backend's sub-domain names (ConnectionAuthScheme/ConnectionProviderKind/ ConnectionCreateData/ConnectionStatus) and dropped the Tool* variants, breaking the @agenta/entities build (TS2694/TS2724) in gatewayTool/core/types.ts. Point the four package-local Tool* aliases at the new names; the aliases keep the gatewayTool package's public surface stable. Verified: @agenta/entities builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…schema leak The tools/triggers domains subclass the shared gateway Connection/Catalog DTOs so their public surface is Tool*/Trigger*-named (per the design comments in each dtos.py). But the subclasses were empty (`pass`), so every inherited leaf type still resolved to its gateway name in the OpenAPI schema and leaked into the generated clients: ConnectionProviderKind/ConnectionStatus/ConnectionCreateData/ ConnectionAuthScheme (via Connection/ConnectionCreate) and CatalogProviderKind/ CatalogAuthScheme (via CatalogProvider/CatalogIntegration). The domains even redefined Tool/TriggerProviderKind and ToolAuthScheme but never referenced them. Override the leaked fields so each domain fully shadows the gateway leaves: - ToolConnection/TriggerConnection: provider_key + status -> domain-named - Tool/TriggerConnectionCreate: provider_key + data -> domain-named - add Tool/TriggerConnectionStatus, Tool/TriggerConnectionCreateData subclasses - add TriggerAuthScheme; point catalog provider.key/integration.auth_schemes at the domain enums Verified against the live /openapi.json: zero bare Connection*/Catalog* names remain; all connection/catalog schema types are Tool*/Trigger*-prefixed. Regenerated both clients (gateway leaf types deleted, domain types added) and re-pointed the gatewayTool/core/types.ts aliases back to the honest Tool* names. py-run-tests --api (1876) + --sdk (1007) green; web --ui unit+integration green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No description provided.