Files
nihalashetty ae67bff5a3 feat: deep-agent canvas, live observability, and multi-environment tooling
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
2026-07-28 01:49:19 +05:30

75 lines
3.2 KiB
Python

"""Trigger sync + scheduling + webhook dispatch (end-to-end via services)."""
from __future__ import annotations
from datetime import datetime, timedelta
from langgraph.checkpoint.memory import InMemorySaver
from forge.db.base import SessionLocal
from forge.models import Trigger, Workflow
from forge.services.dispatch import dispatch_trigger
from forge.services.runs import RunService
from forge.services.triggers import TriggerService
_WEBHOOK_WF = {
"id": "wf_hook", "version": 1,
"state": {"messages": {"type": "list[message]", "reducer": "add_messages"}},
"entry_node": "hook",
"nodes": [
{"id": "hook", "type": "webhook_in", "config": {"message_path": "text"}},
{"id": "agent", "type": "agent", "config": {"flavor": "agent", "model": "fake:Done."}},
{"id": "end", "type": "end", "config": {}},
],
"edges": [{"source": "hook", "target": "agent"}, {"source": "agent", "target": "end"}],
}
async def _make_wf(tenant="t_trig", project="p_trig") -> Workflow:
async with SessionLocal() as s:
wf = Workflow(tenant_id=tenant, project_id=project, name="Hooked", executable=_WEBHOOK_WF, status="active")
s.add(wf)
await s.commit()
await s.refresh(wf)
await TriggerService.sync_from_workflow(s, wf)
return wf
async def test_sync_creates_webhook_trigger_with_key():
wf = await _make_wf()
async with SessionLocal() as s:
trigs = (await s.execute(Trigger.__table__.select().where(Trigger.workflow_id == wf.id))).fetchall()
assert len(trigs) == 1
async with SessionLocal() as s:
t = await TriggerService.by_key(s, trigs[0].key)
assert t is not None and t.kind == "webhook_in" and t.key
def test_build_input_message_path_extracts_field():
t = Trigger(tenant_id="t", project_id="p", workflow_id="w", node_id="hook", kind="webhook_in", config={"message_path": "text"})
assert TriggerService.build_input(t, {"text": "hello there"}) == {"messages": [{"role": "user", "content": "hello there"}]}
def test_build_input_schedule_uses_config_message():
t = Trigger(tenant_id="t", project_id="p", workflow_id="w", node_id="s", kind="schedule", config={"message": "tick"})
assert TriggerService.build_input(t, None)["messages"][0]["content"] == "tick"
def test_is_due_interval():
t = Trigger(tenant_id="t", project_id="p", workflow_id="w", node_id="s", kind="schedule", config={"every_minutes": 10}, enabled=True)
assert TriggerService.is_due(t, datetime.utcnow()) is True # never fired -> due
t.last_fired_at = datetime.utcnow()
assert TriggerService.is_due(t, datetime.utcnow()) is False
t.last_fired_at = datetime.utcnow() - timedelta(minutes=11)
assert TriggerService.is_due(t, datetime.utcnow()) is True
async def test_dispatch_webhook_runs_workflow():
wf = await _make_wf("t_d", "p_d")
async with SessionLocal() as s:
trig = (await s.execute(Trigger.__table__.select().where(Trigger.workflow_id == wf.id))).fetchone()
trigger = await TriggerService.by_key(s, trig.key)
rs = RunService(checkpointer=InMemorySaver())
result = await dispatch_trigger(rs, trigger, {"text": "ping"})
assert result.get("answer") == "Done." and result.get("status") == "done"