From 8ec4ec87c23a3ad494aeecf9fe42075d1cee84be Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sat, 1 Nov 2025 12:41:32 +0900 Subject: [PATCH 01/33] WebSocket notifications backend --- .../backend/backend/server/conn_manager.py | 32 ++++++++++++++++--- .../backend/server/conn_manager_test.py | 23 +++++++++++-- .../backend/backend/server/model.py | 1 + .../backend/backend/server/ws_api.py | 4 +-- .../backend/backend/server/ws_api_test.py | 24 ++++++++++---- 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/autogpt_platform/backend/backend/server/conn_manager.py b/autogpt_platform/backend/backend/server/conn_manager.py index 043002861017..239ea683e92f 100644 --- a/autogpt_platform/backend/backend/server/conn_manager.py +++ b/autogpt_platform/backend/backend/server/conn_manager.py @@ -1,4 +1,4 @@ -from typing import Dict, Set +from typing import Any, Dict, Set from fastapi import WebSocket @@ -19,15 +19,24 @@ class ConnectionManager: def __init__(self): self.active_connections: Set[WebSocket] = set() self.subscriptions: Dict[str, Set[WebSocket]] = {} + self.user_connections: Dict[str, Set[WebSocket]] = {} - async def connect_socket(self, websocket: WebSocket): + async def connect_socket(self, websocket: WebSocket, *, user_id: str): await websocket.accept() self.active_connections.add(websocket) + if user_id not in self.user_connections: + self.user_connections[user_id] = set() + self.user_connections[user_id].add(websocket) - def disconnect_socket(self, websocket: WebSocket): - self.active_connections.remove(websocket) + def disconnect_socket(self, websocket: WebSocket, *, user_id: str): + self.active_connections.discard(websocket) for subscribers in self.subscriptions.values(): subscribers.discard(websocket) + user_conns = self.user_connections.get(user_id) + if user_conns is not None: + user_conns.discard(websocket) + if not user_conns: + del self.user_connections[user_id] async def subscribe_graph_exec( self, *, user_id: str, graph_exec_id: str, websocket: WebSocket @@ -92,6 +101,21 @@ async def send_execution_update( return n_sent + async def send_notification( + self, *, user_id: str, payload: dict[str, Any] | list[Any] | str + ) -> int: + """Send a notification to all websocket connections belonging to a user.""" + message = WSMessage( + method=WSMethod.NOTIFICATION, + data=payload, + ).model_dump_json() + + connections = self.user_connections.get(user_id, set()) + for connection in connections: + await connection.send_text(message) + + return len(connections) + async def _subscribe(self, channel_key: str, websocket: WebSocket) -> str: if channel_key not in self.subscriptions: self.subscriptions[channel_key] = set() diff --git a/autogpt_platform/backend/backend/server/conn_manager_test.py b/autogpt_platform/backend/backend/server/conn_manager_test.py index 401a9eaf8140..f5f4e833ad4e 100644 --- a/autogpt_platform/backend/backend/server/conn_manager_test.py +++ b/autogpt_platform/backend/backend/server/conn_manager_test.py @@ -29,8 +29,9 @@ def mock_websocket() -> AsyncMock: async def test_connect( connection_manager: ConnectionManager, mock_websocket: AsyncMock ) -> None: - await connection_manager.connect_socket(mock_websocket) + await connection_manager.connect_socket(mock_websocket, user_id="user-1") assert mock_websocket in connection_manager.active_connections + assert mock_websocket in connection_manager.user_connections["user-1"] mock_websocket.accept.assert_called_once() @@ -39,11 +40,13 @@ def test_disconnect( ) -> None: connection_manager.active_connections.add(mock_websocket) connection_manager.subscriptions["test_channel_42"] = {mock_websocket} + connection_manager.user_connections["user-1"] = {mock_websocket} - connection_manager.disconnect_socket(mock_websocket) + connection_manager.disconnect_socket(mock_websocket, user_id="user-1") assert mock_websocket not in connection_manager.active_connections assert mock_websocket not in connection_manager.subscriptions["test_channel_42"] + assert "user-1" not in connection_manager.user_connections @pytest.mark.asyncio @@ -207,3 +210,19 @@ async def test_send_execution_result_no_subscribers( await connection_manager.send_execution_update(result) mock_websocket.send_text.assert_not_called() + + +@pytest.mark.asyncio +async def test_send_notification( + connection_manager: ConnectionManager, mock_websocket: AsyncMock +) -> None: + connection_manager.user_connections["user-1"] = {mock_websocket} + + await connection_manager.send_notification( + user_id="user-1", payload={"message": "hey"} + ) + + mock_websocket.send_text.assert_called_once() + sent_message = mock_websocket.send_text.call_args[0][0] + assert '"method":"notification"' in sent_message + assert '"message":"hey"' in sent_message diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index bbb904a794b3..f4dfbd552dd9 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -14,6 +14,7 @@ class WSMethod(enum.Enum): UNSUBSCRIBE = "unsubscribe" GRAPH_EXECUTION_EVENT = "graph_execution_event" NODE_EXECUTION_EVENT = "node_execution_event" + NOTIFICATION = "notification" ERROR = "error" HEARTBEAT = "heartbeat" diff --git a/autogpt_platform/backend/backend/server/ws_api.py b/autogpt_platform/backend/backend/server/ws_api.py index 902b71b0ceb6..834628ee6804 100644 --- a/autogpt_platform/backend/backend/server/ws_api.py +++ b/autogpt_platform/backend/backend/server/ws_api.py @@ -228,7 +228,7 @@ async def websocket_router( user_id = await authenticate_websocket(websocket) if not user_id: return - await manager.connect_socket(websocket) + await manager.connect_socket(websocket, user_id=user_id) # Track WebSocket connection update_websocket_connections(user_id, 1) @@ -301,7 +301,7 @@ async def websocket_router( ) except WebSocketDisconnect: - manager.disconnect_socket(websocket) + manager.disconnect_socket(websocket, user_id=user_id) logger.debug("WebSocket client disconnected") finally: update_websocket_connections(user_id, -1) diff --git a/autogpt_platform/backend/backend/server/ws_api_test.py b/autogpt_platform/backend/backend/server/ws_api_test.py index c9c27eb0867e..cfffdeb85cf1 100644 --- a/autogpt_platform/backend/backend/server/ws_api_test.py +++ b/autogpt_platform/backend/backend/server/ws_api_test.py @@ -53,7 +53,9 @@ async def test_websocket_router_subscribe( cast(WebSocket, mock_websocket), cast(ConnectionManager, mock_manager) ) - mock_manager.connect_socket.assert_called_once_with(mock_websocket) + mock_manager.connect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) mock_manager.subscribe_graph_exec.assert_called_once_with( user_id=DEFAULT_USER_ID, graph_exec_id="test-graph-exec-1", @@ -72,7 +74,9 @@ async def test_websocket_router_subscribe( snapshot.snapshot_dir = "snapshots" snapshot.assert_match(json.dumps(parsed_message, indent=2, sort_keys=True), "sub") - mock_manager.disconnect_socket.assert_called_once_with(mock_websocket) + mock_manager.disconnect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) @pytest.mark.asyncio @@ -99,7 +103,9 @@ async def test_websocket_router_unsubscribe( cast(WebSocket, mock_websocket), cast(ConnectionManager, mock_manager) ) - mock_manager.connect_socket.assert_called_once_with(mock_websocket) + mock_manager.connect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) mock_manager.unsubscribe_graph_exec.assert_called_once_with( user_id=DEFAULT_USER_ID, graph_exec_id="test-graph-exec-1", @@ -115,7 +121,9 @@ async def test_websocket_router_unsubscribe( snapshot.snapshot_dir = "snapshots" snapshot.assert_match(json.dumps(parsed_message, indent=2, sort_keys=True), "unsub") - mock_manager.disconnect_socket.assert_called_once_with(mock_websocket) + mock_manager.disconnect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) @pytest.mark.asyncio @@ -136,11 +144,15 @@ async def test_websocket_router_invalid_method( cast(WebSocket, mock_websocket), cast(ConnectionManager, mock_manager) ) - mock_manager.connect_socket.assert_called_once_with(mock_websocket) + mock_manager.connect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) mock_websocket.send_text.assert_called_once() assert '"method":"error"' in mock_websocket.send_text.call_args[0][0] assert '"success":false' in mock_websocket.send_text.call_args[0][0] - mock_manager.disconnect_socket.assert_called_once_with(mock_websocket) + mock_manager.disconnect_socket.assert_called_once_with( + mock_websocket, user_id=DEFAULT_USER_ID + ) @pytest.mark.asyncio From f56a3c89cad5e79205fb06b45d2e32d7b9710499 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sat, 1 Nov 2025 12:44:41 +0900 Subject: [PATCH 02/33] Frontend types --- .../frontend/src/lib/autogpt-server-api/client.ts | 2 ++ .../frontend/src/lib/autogpt-server-api/types.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 08e5c05f633b..d6d94edf6e71 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -66,6 +66,7 @@ import type { UserOnboarding, UserPasswordCredentials, UsersBalanceHistoryResponse, + WebSocketNotification, } from "./types"; import { environment } from "@/services/environment"; @@ -1323,6 +1324,7 @@ type WebsocketMessageTypeMap = { subscribe_graph_executions: { graph_id: GraphID }; graph_execution_event: GraphExecution; node_execution_event: NodeExecutionResult; + notification: WebSocketNotification; heartbeat: "ping" | "pong"; }; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index 1419ffb68691..b274c182d800 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -976,6 +976,20 @@ export interface UserOnboarding { agentRuns: number; } +export interface OnboardingNotificationPayload { + type: "onboarding"; + event: "trigger_webhook_completed"; + step: OnboardingStep; +} + +export type WebSocketNotification = + | OnboardingNotificationPayload + | { + type: string; + event?: string; + [key: string]: unknown; + }; + /* *** UTILITIES *** */ /** Use branded types for IDs -> deny mixing IDs between different object classes */ From f7e57efe7f6257b96f546c601abdd43703bd7edc Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 5 Nov 2025 09:55:28 +0900 Subject: [PATCH 03/33] Feedback --- .../backend/backend/server/conn_manager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/autogpt_platform/backend/backend/server/conn_manager.py b/autogpt_platform/backend/backend/server/conn_manager.py index 239ea683e92f..3b86036beccd 100644 --- a/autogpt_platform/backend/backend/server/conn_manager.py +++ b/autogpt_platform/backend/backend/server/conn_manager.py @@ -1,3 +1,4 @@ +import asyncio from typing import Any, Dict, Set from fastapi import WebSocket @@ -36,7 +37,7 @@ def disconnect_socket(self, websocket: WebSocket, *, user_id: str): if user_conns is not None: user_conns.discard(websocket) if not user_conns: - del self.user_connections[user_id] + self.user_connections.pop(user_id, None) async def subscribe_graph_exec( self, *, user_id: str, graph_exec_id: str, websocket: WebSocket @@ -110,9 +111,14 @@ async def send_notification( data=payload, ).model_dump_json() - connections = self.user_connections.get(user_id, set()) - for connection in connections: - await connection.send_text(message) + connections = tuple(self.user_connections.get(user_id, set())) + if not connections: + return 0 + + await asyncio.gather( + *(connection.send_text(message) for connection in connections), + return_exceptions=True + ) return len(connections) From e38d1c560a586b44bc77e54a9e82cdd080f1087f Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 5 Nov 2025 10:51:29 +0900 Subject: [PATCH 04/33] Update types --- .../backend/backend/server/conn_manager.py | 10 +++++----- .../backend/backend/server/conn_manager_test.py | 11 +++++++---- autogpt_platform/backend/backend/server/model.py | 9 +++++++++ .../frontend/src/lib/autogpt-server-api/types.ts | 4 ++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/autogpt_platform/backend/backend/server/conn_manager.py b/autogpt_platform/backend/backend/server/conn_manager.py index 3b86036beccd..8d6511756414 100644 --- a/autogpt_platform/backend/backend/server/conn_manager.py +++ b/autogpt_platform/backend/backend/server/conn_manager.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Dict, Set +from typing import Dict, Set from fastapi import WebSocket @@ -8,7 +8,7 @@ GraphExecutionEvent, NodeExecutionEvent, ) -from backend.server.model import WSMessage, WSMethod +from backend.server.model import NotificationPayload, WSMessage, WSMethod _EVENT_TYPE_TO_METHOD_MAP: dict[ExecutionEventType, WSMethod] = { ExecutionEventType.GRAPH_EXEC_UPDATE: WSMethod.GRAPH_EXECUTION_EVENT, @@ -103,12 +103,12 @@ async def send_execution_update( return n_sent async def send_notification( - self, *, user_id: str, payload: dict[str, Any] | list[Any] | str + self, *, user_id: str, payload: NotificationPayload ) -> int: """Send a notification to all websocket connections belonging to a user.""" message = WSMessage( method=WSMethod.NOTIFICATION, - data=payload, + data=payload.model_dump(), ).model_dump_json() connections = tuple(self.user_connections.get(user_id, set())) @@ -117,7 +117,7 @@ async def send_notification( await asyncio.gather( *(connection.send_text(message) for connection in connections), - return_exceptions=True + return_exceptions=True, ) return len(connections) diff --git a/autogpt_platform/backend/backend/server/conn_manager_test.py b/autogpt_platform/backend/backend/server/conn_manager_test.py index f5f4e833ad4e..379928fae7dd 100644 --- a/autogpt_platform/backend/backend/server/conn_manager_test.py +++ b/autogpt_platform/backend/backend/server/conn_manager_test.py @@ -10,7 +10,7 @@ NodeExecutionEvent, ) from backend.server.conn_manager import ConnectionManager -from backend.server.model import WSMessage, WSMethod +from backend.server.model import NotificationPayload, WSMessage, WSMethod @pytest.fixture @@ -219,10 +219,13 @@ async def test_send_notification( connection_manager.user_connections["user-1"] = {mock_websocket} await connection_manager.send_notification( - user_id="user-1", payload={"message": "hey"} + user_id="user-1", payload=NotificationPayload(type="info", event="hey") ) mock_websocket.send_text.assert_called_once() sent_message = mock_websocket.send_text.call_args[0][0] - assert '"method":"notification"' in sent_message - assert '"message":"hey"' in sent_message + expected_message = WSMessage( + method=WSMethod.NOTIFICATION, + data={"type": "info", "event": "hey"}, + ).model_dump_json() + assert sent_message == expected_message diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index f4dfbd552dd9..24ba4fa7eeff 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -77,3 +77,12 @@ class TimezoneResponse(pydantic.BaseModel): class UpdateTimezoneRequest(pydantic.BaseModel): timezone: TimeZoneName + + +class NotificationPayload(pydantic.BaseModel): + type: str + event: str + + +class OnboardingNotificationPayload(NotificationPayload): + step: str diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index b274c182d800..ea82e34d3ed1 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -978,7 +978,7 @@ export interface UserOnboarding { export interface OnboardingNotificationPayload { type: "onboarding"; - event: "trigger_webhook_completed"; + event: string; step: OnboardingStep; } @@ -986,7 +986,7 @@ export type WebSocketNotification = | OnboardingNotificationPayload | { type: string; - event?: string; + event: string; [key: string]: unknown; }; From 61c65a5b71787bc8678cba40dce087e9f4e6b9e8 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 6 Nov 2025 11:04:35 +0900 Subject: [PATCH 05/33] Redis notification bus --- .../backend/backend/data/notification_bus.py | 33 +++++++++++++++++++ .../backend/backend/server/ws_api.py | 19 +++++++++-- .../backend/backend/util/settings.py | 5 +++ 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 autogpt_platform/backend/backend/data/notification_bus.py diff --git a/autogpt_platform/backend/backend/data/notification_bus.py b/autogpt_platform/backend/backend/data/notification_bus.py new file mode 100644 index 000000000000..ddd0681a2c13 --- /dev/null +++ b/autogpt_platform/backend/backend/data/notification_bus.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import AsyncGenerator + +from pydantic import BaseModel + +from backend.data.event_bus import AsyncRedisEventBus +from backend.server.model import NotificationPayload +from backend.util.settings import Settings + + +class NotificationEvent(BaseModel): + """Generic notification event destined for websocket delivery.""" + + user_id: str + payload: NotificationPayload + + +class AsyncRedisNotificationEventBus(AsyncRedisEventBus[NotificationEvent]): + Model = NotificationEvent # type: ignore + + @property + def event_bus_name(self) -> str: + return Settings().config.notification_event_bus_name + + async def publish(self, event: NotificationEvent) -> None: + await self.publish_event(event, event.user_id) + + async def listen( + self, user_id: str = "*" + ) -> AsyncGenerator[NotificationEvent, None]: + async for event in self.listen_events(user_id): + yield event diff --git a/autogpt_platform/backend/backend/server/ws_api.py b/autogpt_platform/backend/backend/server/ws_api.py index 834628ee6804..1a4b418bf78f 100644 --- a/autogpt_platform/backend/backend/server/ws_api.py +++ b/autogpt_platform/backend/backend/server/ws_api.py @@ -10,6 +10,7 @@ from starlette.middleware.cors import CORSMiddleware from backend.data.execution import AsyncRedisExecutionEventBus +from backend.data.notification_bus import AsyncRedisNotificationEventBus from backend.data.user import DEFAULT_USER_ID from backend.monitoring.instrumentation import ( instrument_fastapi, @@ -61,9 +62,21 @@ def get_connection_manager(): @continuous_retry() async def event_broadcaster(manager: ConnectionManager): - event_queue = AsyncRedisExecutionEventBus() - async for event in event_queue.listen("*"): - await manager.send_execution_update(event) + execution_bus = AsyncRedisExecutionEventBus() + notification_bus = AsyncRedisNotificationEventBus() + + async def execution_worker(): + async for event in execution_bus.listen("*"): + await manager.send_execution_update(event) + + async def notification_worker(): + async for notification in notification_bus.listen("*"): + await manager.send_notification( + user_id=notification.user_id, + payload=notification.payload, + ) + + await asyncio.gather(execution_worker(), notification_worker()) async def authenticate_websocket(websocket: WebSocket) -> str: diff --git a/autogpt_platform/backend/backend/util/settings.py b/autogpt_platform/backend/backend/util/settings.py index f07f998a38dc..5a00dd0f8986 100644 --- a/autogpt_platform/backend/backend/util/settings.py +++ b/autogpt_platform/backend/backend/util/settings.py @@ -412,6 +412,11 @@ def validate_platform_base_url(cls, v: str, info: ValidationInfo) -> str: description="Name of the event bus", ) + notification_event_bus_name: str = Field( + default="notification_event", + description="Name of the websocket notification event bus", + ) + trust_endpoints_for_requests: List[str] = Field( default_factory=list, description="A whitelist of trusted internal endpoints for the backend to make requests to.", From e019233fc7ab5bb028576b6ac30c66d90dbb4685 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Fri, 7 Nov 2025 11:06:49 +0900 Subject: [PATCH 06/33] Confetti on ws notification --- .../backend/backend/data/onboarding.py | 56 ++++++++------- .../backend/server/integrations/router.py | 4 +- .../Navbar/components/Wallet/Wallet.tsx | 71 +++++++++---------- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 6bfc9b494ddc..cf367af1014f 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -11,6 +11,11 @@ from backend.data.block import get_blocks from backend.data.credit import get_user_credit_model from backend.data.model import CredentialsMetaInput +from backend.data.notification_bus import ( + AsyncRedisNotificationEventBus, + NotificationEvent, +) +from backend.server.model import OnboardingNotificationPayload from backend.server.v2.store.model import StoreAgentDetails from backend.util.cache import cached from backend.util.json import SafeJson @@ -54,24 +59,13 @@ async def get_user_onboarding(user_id: str): async def update_user_onboarding(user_id: str, data: UserOnboardingUpdate): update: UserOnboardingUpdateInput = {} + onboarding = await get_user_onboarding(user_id) if data.completedSteps is not None: update["completedSteps"] = list(set(data.completedSteps)) - for step in ( - OnboardingStep.AGENT_NEW_RUN, - OnboardingStep.MARKETPLACE_VISIT, - OnboardingStep.MARKETPLACE_ADD_AGENT, - OnboardingStep.MARKETPLACE_RUN_AGENT, - OnboardingStep.BUILDER_SAVE_AGENT, - OnboardingStep.RE_RUN_AGENT, - OnboardingStep.SCHEDULE_AGENT, - OnboardingStep.RUN_AGENTS, - OnboardingStep.RUN_3_DAYS, - OnboardingStep.TRIGGER_WEBHOOK, - OnboardingStep.RUN_14_DAYS, - OnboardingStep.RUN_AGENTS_100, - ): - if step in data.completedSteps: - await reward_user(user_id, step) + for step in data.completedSteps: + if step not in onboarding.completedSteps: + await _reward_user(user_id, onboarding, step) + await _send_onboarding_notification(user_id, step) if data.walletShown is not None: update["walletShown"] = data.walletShown if data.notified is not None: @@ -104,7 +98,7 @@ async def update_user_onboarding(user_id: str, data: UserOnboardingUpdate): ) -async def reward_user(user_id: str, step: OnboardingStep): +async def _reward_user(user_id: str, onboarding: UserOnboarding, step: OnboardingStep): reward = 0 match step: # Reward user when they clicked New Run during onboarding @@ -138,8 +132,6 @@ async def reward_user(user_id: str, step: OnboardingStep): if reward == 0: return - onboarding = await get_user_onboarding(user_id) - # Skip if already rewarded if step in onboarding.rewardedFor: return @@ -156,20 +148,32 @@ async def reward_user(user_id: str, step: OnboardingStep): ) -async def complete_webhook_trigger_step(user_id: str): +async def complete_onboarding_step(user_id: str, step: OnboardingStep): """ - Completes the TRIGGER_WEBHOOK onboarding step for the user if not already completed. + Completes the specified onboarding step for the user if not already completed. """ onboarding = await get_user_onboarding(user_id) - if OnboardingStep.TRIGGER_WEBHOOK not in onboarding.completedSteps: + if step not in onboarding.completedSteps: await update_user_onboarding( user_id, - UserOnboardingUpdate( - completedSteps=onboarding.completedSteps - + [OnboardingStep.TRIGGER_WEBHOOK] - ), + UserOnboardingUpdate(completedSteps=onboarding.completedSteps + [step]), ) + await _send_onboarding_notification(user_id, step) + + +async def _send_onboarding_notification(user_id: str, step: OnboardingStep): + """ + Sends an onboarding notification to the user for the specified step. + """ + payload = OnboardingNotificationPayload( + type="onboarding", + event="step_completed", + step=step.value, + ) + await AsyncRedisNotificationEventBus().publish( + NotificationEvent(user_id=user_id, payload=payload) + ) def clean_and_split(text: str) -> list[str]: diff --git a/autogpt_platform/backend/backend/server/integrations/router.py b/autogpt_platform/backend/backend/server/integrations/router.py index 012bcf9ff419..9e4c1dc8b573 100644 --- a/autogpt_platform/backend/backend/server/integrations/router.py +++ b/autogpt_platform/backend/backend/server/integrations/router.py @@ -33,7 +33,7 @@ OAuth2Credentials, UserIntegrations, ) -from backend.data.onboarding import complete_webhook_trigger_step +from backend.data.onboarding import OnboardingStep, complete_onboarding_step from backend.data.user import get_user_integrations from backend.executor.utils import add_graph_execution from backend.integrations.ayrshare import AyrshareClient, SocialPlatform @@ -376,7 +376,7 @@ async def webhook_ingress_generic( if not (webhook.triggered_nodes or webhook.triggered_presets): return - await complete_webhook_trigger_step(user_id) + await complete_onboarding_step(user_id, OnboardingStep.TRIGGER_WEBHOOK) # Execute all triggers concurrently for better performance tasks = [] diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx index 8f370166ac1e..549266f66f1f 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx @@ -8,7 +8,10 @@ import { import { ScrollArea } from "@/components/__legacy__/ui/scroll-area"; import { Text } from "@/components/atoms/Text/Text"; import useCredits from "@/hooks/useCredits"; -import { OnboardingStep } from "@/lib/autogpt-server-api"; +import { + OnboardingStep, + WebSocketNotification, +} from "@/lib/autogpt-server-api"; import { cn } from "@/lib/utils"; import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag"; @@ -20,6 +23,7 @@ import * as party from "party-js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { WalletRefill } from "./components/WalletRefill"; import { TaskGroups } from "./components/WalletTaskGroups"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export interface Task { id: OnboardingStep; @@ -163,6 +167,7 @@ export function Wallet() { ]; }, [state]); + const api = useBackendAPI(); const { credits, formatCredits, fetchCredits } = useCredits({ fetchInitialCredits: true, }); @@ -176,12 +181,8 @@ export function Wallet() { return groups.reduce((acc, group) => acc + group.tasks.length, 0); }, [groups]); - // Get total completed count for all groups + // Total completed task count across all groups const [completedCount, setCompletedCount] = useState(null); - // Needed to show confetti when a new step is completed - const [prevCompletedCount, setPrevCompletedCount] = useState( - null, - ); const walletRef = useRef(null); @@ -250,27 +251,18 @@ export function Wallet() { ); // Confetti effect on the wallet button - useEffect(() => { - // It's enough to check completed count, - // because the order of completed steps is not important - // If the count is the same, we don't need to do anything - if (completedCount === null || completedCount === prevCompletedCount) { - return; - } - // Otherwise, we need to set the new prevCompletedCount - setPrevCompletedCount(completedCount); - // If there was no previous count, we don't show confetti - if (prevCompletedCount === null) { - return; - } - // And emit confetti - if (walletRef.current) { - // Fix confetti appearing in the top left corner - const rect = walletRef.current.getBoundingClientRect(); - if (rect.width === 0 || rect.height === 0) { + const handleNotification = useCallback( + (notification: WebSocketNotification) => { + if (notification.type !== "onboarding") { return; } - setTimeout(() => { + + if (walletRef.current) { + // Fix confetti appearing in the top left corner + const rect = walletRef.current.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) { + return; + } fetchCredits(); party.confetti(walletRef.current!, { count: 30, @@ -280,17 +272,24 @@ export function Wallet() { speed: party.variation.range(200, 300), modules: [fadeOut], }); - }, 800); - } - }, [ - state?.completedSteps, - state?.notified, - fadeOut, - fetchCredits, - completedCount, - prevCompletedCount, - walletRef, - ]); + } + }, + [], + ); + + // WebSocket setup for onboarding notifications + useEffect(() => { + const detachMessage = api.onWebSocketMessage( + "notification", + handleNotification, + ); + + api.connectWebSocket(); + + return () => { + detachMessage(); + }; + }, [api, handleNotification]); // Wallet flash on credits change useEffect(() => { From 59d652cf954fa207d4af1cb0dcb37d36699c3a3e Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Fri, 7 Nov 2025 11:47:01 +0900 Subject: [PATCH 07/33] Scroll completed task into view --- .../Wallet/components/WalletTaskGroups.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx index 1b1cd38b6ec7..d3cbd0a2b9b4 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx @@ -70,6 +70,14 @@ export function TaskGroups({ groups }: Props) { } }; + const scrollIntoViewCentered = useCallback((el: HTMLDivElement) => { + el.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "nearest", + }); + }, []); + const delayConfetti = useCallback((el: HTMLDivElement, count: number) => { setTimeout(() => { party.confetti(el, { @@ -100,7 +108,8 @@ export function TaskGroups({ groups }: Props) { if (groupCompleted) { const el = refs.current[group.name]; if (el && !alreadyCelebrated) { - delayConfetti(el, 50); + scrollIntoViewCentered(el); + delayConfetti(el, 600); // Update the state to include all group tasks as notified // This ensures that the confetti effect isn't perpetually triggered on Wallet const notifiedTasks = group.tasks.map((task) => task.id); @@ -114,7 +123,8 @@ export function TaskGroups({ groups }: Props) { group.tasks.forEach((task) => { const el = refs.current[task.id]; if (el && isTaskCompleted(task) && !state?.notified.includes(task.id)) { - delayConfetti(el, 40); + scrollIntoViewCentered(el); + delayConfetti(el, 400); // Update the state to include the task as notified updateState({ notified: [...(state?.notified || []), task.id] }); } @@ -128,6 +138,7 @@ export function TaskGroups({ groups }: Props) { state?.notified, isGroupCompleted, isTaskCompleted, + scrollIntoViewCentered, ]); return ( From 16164d50c40e6f79961b39c5c1fe368558aec7e6 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Fri, 7 Nov 2025 11:47:40 +0900 Subject: [PATCH 08/33] Complete rerun task on Run again --- .../OldAgentLibraryView/components/agent-run-details-view.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx index 2fa24471a868..c00947500646 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx @@ -38,6 +38,7 @@ import { AgentRunStatus, agentRunStatusMap } from "./agent-run-status-chip"; import useCredits from "@/hooks/useCredits"; import { AgentRunOutputView } from "./agent-run-output-view"; import { analytics } from "@/services/analytics"; +import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; export function AgentRunDetailsView({ agent, @@ -64,6 +65,8 @@ export function AgentRunDetailsView({ [run], ); + const { completeStep } = useOnboarding(); + const toastOnFail = useToastOnFail(); const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => { @@ -154,6 +157,7 @@ export function AgentRunDetailsView({ name: graph.name, id: graph.id, }); + completeStep("RE_RUN_AGENT"); onRun(id); }) .catch(toastOnFail("execute agent")); From f52046937bb4eadcb21b6300524ecf9952114fcb Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 17:06:09 +0900 Subject: [PATCH 09/33] Update endpoints --- .../backend/backend/server/routers/v1.py | 22 +- .../frontend/src/app/api/openapi.json | 2309 ++++++++++++++--- 2 files changed, 2029 insertions(+), 302 deletions(-) diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 6cf00cbb5fff..24c4fd9b20a6 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -27,7 +27,9 @@ from pydantic import BaseModel from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND from typing_extensions import Optional, TypedDict +from prisma.models import UserOnboarding +from backend.server.v2.store.model import StoreAgentDetails import backend.server.integrations.router import backend.server.routers.analytics import backend.server.v2.library.db as library_db @@ -49,6 +51,8 @@ from backend.data.notifications import NotificationPreference, NotificationPreferenceDTO from backend.data.onboarding import ( UserOnboardingUpdate, + OnboardingStep, + complete_onboarding_step, get_recommended_agents, get_user_onboarding, onboarding_enabled, @@ -218,45 +222,45 @@ async def update_preferences( @v1_router.get( "/onboarding", - summary="Get onboarding status", + summary="Onboarding state", tags=["onboarding"], dependencies=[Security(requires_user)], ) -async def get_onboarding(user_id: Annotated[str, Security(get_user_id)]): +async def get_onboarding(user_id: Annotated[str, Security(get_user_id)]) -> UserOnboarding: return await get_user_onboarding(user_id) @v1_router.patch( "/onboarding", - summary="Update onboarding progress", + summary="Update onboarding state", tags=["onboarding"], dependencies=[Security(requires_user)], ) async def update_onboarding( user_id: Annotated[str, Security(get_user_id)], data: UserOnboardingUpdate -): +) -> UserOnboarding: return await update_user_onboarding(user_id, data) @v1_router.get( "/onboarding/agents", - summary="Get recommended agents", + summary="Recommended onboarding agents", tags=["onboarding"], dependencies=[Security(requires_user)], ) async def get_onboarding_agents( user_id: Annotated[str, Security(get_user_id)], -): +) -> list[StoreAgentDetails]: return await get_recommended_agents(user_id) @v1_router.get( "/onboarding/enabled", - summary="Check onboarding enabled", + summary="Is onboarding enabled", tags=["onboarding", "public"], dependencies=[Security(requires_user)], ) -async def is_onboarding_enabled(): +async def is_onboarding_enabled() -> bool: return await onboarding_enabled() @@ -266,7 +270,7 @@ async def is_onboarding_enabled(): tags=["onboarding"], dependencies=[Security(requires_user)], ) -async def reset_onboarding(user_id: Annotated[str, Security(get_user_id)]): +async def reset_onboarding(user_id: Annotated[str, Security(get_user_id)]) -> UserOnboarding: return await reset_user_onboarding(user_id) diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index 208fb61c00b2..50722d32c6c9 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -827,12 +827,16 @@ "/api/onboarding": { "get": { "tags": ["v1", "onboarding"], - "summary": "Get onboarding status", - "operationId": "getV1Get onboarding status", + "summary": "Onboarding state", + "operationId": "getV1Onboarding state", "responses": { "200": { "description": "Successful Response", - "content": { "application/json": { "schema": {} } } + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserOnboarding" } + } + } }, "401": { "$ref": "#/components/responses/HTTP401NotAuthenticatedError" @@ -842,8 +846,8 @@ }, "patch": { "tags": ["v1", "onboarding"], - "summary": "Update onboarding progress", - "operationId": "patchV1Update onboarding progress", + "summary": "Update onboarding state", + "operationId": "patchV1Update onboarding state", "requestBody": { "content": { "application/json": { @@ -855,7 +859,11 @@ "responses": { "200": { "description": "Successful Response", - "content": { "application/json": { "schema": {} } } + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserOnboarding" } + } + } }, "422": { "description": "Validation Error", @@ -875,12 +883,20 @@ "/api/onboarding/agents": { "get": { "tags": ["v1", "onboarding"], - "summary": "Get recommended agents", - "operationId": "getV1Get recommended agents", + "summary": "Recommended onboarding agents", + "operationId": "getV1Recommended onboarding agents", "responses": { "200": { "description": "Successful Response", - "content": { "application/json": { "schema": {} } } + "content": { + "application/json": { + "schema": { + "items": { "$ref": "#/components/schemas/StoreAgentDetails" }, + "type": "array", + "title": "Response Getv1Recommended Onboarding Agents" + } + } + } }, "401": { "$ref": "#/components/responses/HTTP401NotAuthenticatedError" @@ -892,12 +908,19 @@ "/api/onboarding/enabled": { "get": { "tags": ["v1", "onboarding", "public"], - "summary": "Check onboarding enabled", - "operationId": "getV1Check onboarding enabled", + "summary": "Is onboarding enabled", + "operationId": "getV1Is onboarding enabled", "responses": { "200": { "description": "Successful Response", - "content": { "application/json": { "schema": {} } } + "content": { + "application/json": { + "schema": { + "type": "boolean", + "title": "Response Getv1Is Onboarding Enabled" + } + } + } }, "401": { "$ref": "#/components/responses/HTTP401NotAuthenticatedError" @@ -914,7 +937,11 @@ "responses": { "200": { "description": "Successful Response", - "content": { "application/json": { "schema": {} } } + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserOnboarding" } + } + } }, "401": { "$ref": "#/components/responses/HTTP401NotAuthenticatedError" @@ -2537,7 +2564,7 @@ "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/Profile" } + "schema": { "$ref": "#/components/schemas/Profile-Input" } } }, "required": true @@ -4305,7 +4332,9 @@ "description": "Agent added successfully", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/LibraryAgent" } + "schema": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + } } } }, @@ -4408,7 +4437,9 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/LibraryAgent" } + "schema": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + } } } }, @@ -4454,7 +4485,9 @@ "description": "Agent updated successfully", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/LibraryAgent" } + "schema": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + } } } }, @@ -4536,7 +4569,9 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/LibraryAgent" } + "schema": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + } } } }, @@ -4576,7 +4611,9 @@ "application/json": { "schema": { "anyOf": [ - { "$ref": "#/components/schemas/LibraryAgent" }, + { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + }, { "type": "null" } ], "title": "Response Getv2Get Agent By Store Id" @@ -4617,7 +4654,9 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/LibraryAgent" } + "schema": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + } } } }, @@ -4994,6 +5033,69 @@ }, "components": { "schemas": { + "APIKey": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "name": { "type": "string", "title": "Name" }, + "head": { "type": "string", "title": "Head" }, + "tail": { "type": "string", "title": "Tail" }, + "hash": { "type": "string", "title": "Hash" }, + "salt": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Salt" + }, + "status": { "$ref": "#/components/schemas/APIKeyStatus" }, + "permissions": { + "items": { "$ref": "#/components/schemas/APIKeyPermission" }, + "type": "array", + "title": "Permissions" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "lastUsedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Lastusedat" + }, + "revokedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Revokedat" + }, + "description": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Description" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "head", + "tail", + "hash", + "status", + "permissions", + "createdAt", + "userId" + ], + "title": "APIKey", + "description": "Represents a APIKey record" + }, "APIKeyCredentials": { "properties": { "id": { "type": "string", "title": "Id" }, @@ -5101,6 +5203,28 @@ "required": ["new_balance", "transaction_key"], "title": "AddUserCreditsResponse" }, + "AgentBlock": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "name": { "type": "string", "title": "Name" }, + "inputSchema": { "type": "string", "title": "Inputschema" }, + "outputSchema": { "type": "string", "title": "Outputschema" }, + "ReferencedByAgentNode": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNode" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Referencedbyagentnode" + } + }, + "type": "object", + "required": ["id", "name", "inputSchema", "outputSchema"], + "title": "AgentBlock", + "description": "Represents a AgentBlock record" + }, "AgentExecutionStatus": { "type": "string", "enum": [ @@ -5113,159 +5237,811 @@ ], "title": "AgentExecutionStatus" }, - "ApiResponse": { - "properties": { - "answer": { "type": "string", "title": "Answer" }, - "documents": { - "items": { "$ref": "#/components/schemas/Document" }, - "type": "array", - "title": "Documents" - }, - "success": { "type": "boolean", "title": "Success" } - }, - "type": "object", - "required": ["answer", "documents", "success"], - "title": "ApiResponse" - }, - "AutoTopUpConfig": { - "properties": { - "amount": { "type": "integer", "title": "Amount" }, - "threshold": { "type": "integer", "title": "Threshold" } - }, - "type": "object", - "required": ["amount", "threshold"], - "title": "AutoTopUpConfig" - }, - "AyrshareSSOResponse": { + "AgentGraph": { "properties": { - "sso_url": { - "type": "string", - "title": "Sso Url", - "description": "The SSO URL for Ayrshare integration" - }, - "expires_at": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version" }, + "createdAt": { "type": "string", "format": "date-time", - "title": "Expires At", - "description": "ISO timestamp when the URL expires" - } - }, - "type": "object", - "required": ["sso_url", "expires_at"], - "title": "AyrshareSSOResponse" - }, - "BaseGraph-Input": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version", "default": 1 }, - "is_active": { - "type": "boolean", - "title": "Is Active", - "default": true + "title": "Createdat" }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" + "updatedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Updatedat" }, - "recommended_schedule_cron": { + "name": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" - }, - "nodes": { - "items": { "$ref": "#/components/schemas/Node" }, - "type": "array", - "title": "Nodes", - "default": [] - }, - "links": { - "items": { "$ref": "#/components/schemas/Link" }, - "type": "array", - "title": "Links", - "default": [] + "title": "Name" }, - "forked_from_id": { + "description": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Forked From Id" - }, - "forked_from_version": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Forked From Version" - } - }, - "type": "object", - "required": ["name", "description"], - "title": "BaseGraph" - }, - "BaseGraph-Output": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version", "default": 1 }, - "is_active": { - "type": "boolean", - "title": "Is Active", - "default": true + "title": "Description" }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, "instructions": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Instructions" }, - "recommended_schedule_cron": { + "recommendedScheduleCron": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" - }, - "nodes": { - "items": { "$ref": "#/components/schemas/Node" }, - "type": "array", - "title": "Nodes", - "default": [] + "title": "Recommendedschedulecron" }, - "links": { - "items": { "$ref": "#/components/schemas/Link" }, - "type": "array", - "title": "Links", - "default": [] + "isActive": { "type": "boolean", "title": "Isactive" }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] }, - "forked_from_id": { + "forkedFromId": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Forked From Id" + "title": "Forkedfromid" }, - "forked_from_version": { + "forkedFromVersion": { "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Forked From Version" + "title": "Forkedfromversion" }, - "input_schema": { - "additionalProperties": true, - "type": "object", - "title": "Input Schema", - "readOnly": true + "forkedFrom": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] }, - "output_schema": { - "additionalProperties": true, - "type": "object", - "title": "Output Schema", - "readOnly": true + "forks": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentGraph" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Forks" }, - "has_external_trigger": { - "type": "boolean", - "title": "Has External Trigger", - "readOnly": true + "Nodes": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNode" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Nodes" }, - "trigger_setup_info": { + "Executions": { "anyOf": [ - { "$ref": "#/components/schemas/GraphTriggerInfo" }, + { + "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, + "type": "array" + }, { "type": "null" } ], - "readOnly": true - } - }, - "type": "object", - "required": [ - "name", - "description", + "title": "Executions" + }, + "Presets": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentPreset" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Presets" + }, + "LibraryAgents": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/prisma__models__LibraryAgent" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Libraryagents" + }, + "StoreListings": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListing" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Storelistings" + }, + "StoreListingVersions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListingVersion" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Storelistingversions" + } + }, + "type": "object", + "required": ["id", "version", "createdAt", "isActive", "userId"], + "title": "AgentGraph", + "description": "Represents a AgentGraph record" + }, + "AgentGraphExecution": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Updatedat" + }, + "startedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Startedat" + }, + "isDeleted": { "type": "boolean", "title": "Isdeleted" }, + "executionStatus": { + "$ref": "#/components/schemas/AgentExecutionStatus" + }, + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "agentPresetId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Agentpresetid" + }, + "AgentPreset": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentPreset" }, + { "type": "null" } + ] + }, + "inputs": { "anyOf": [{}, { "type": "null" }], "title": "Inputs" }, + "credentialInputs": { + "anyOf": [{}, { "type": "null" }], + "title": "Credentialinputs" + }, + "nodesInputMasks": { + "anyOf": [{}, { "type": "null" }], + "title": "Nodesinputmasks" + }, + "NodeExecutions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNodeExecution" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Nodeexecutions" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "stats": { "anyOf": [{}, { "type": "null" }], "title": "Stats" }, + "parentGraphExecutionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Parentgraphexecutionid" + }, + "ParentExecution": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraphExecution" }, + { "type": "null" } + ] + }, + "ChildExecutions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Childexecutions" + }, + "isShared": { "type": "boolean", "title": "Isshared" }, + "shareToken": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Sharetoken" + }, + "sharedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Sharedat" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "isDeleted", + "executionStatus", + "agentGraphId", + "agentGraphVersion", + "userId", + "isShared" + ], + "title": "AgentGraphExecution", + "description": "Represents a AgentGraphExecution record" + }, + "AgentNode": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "agentBlockId": { "type": "string", "title": "Agentblockid" }, + "AgentBlock": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentBlock" }, + { "type": "null" } + ] + }, + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "Input": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNodeLink" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Input" + }, + "Output": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNodeLink" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Output" + }, + "constantInput": { "title": "Constantinput" }, + "webhookId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Webhookid" + }, + "Webhook": { + "anyOf": [ + { "$ref": "#/components/schemas/IntegrationWebhook" }, + { "type": "null" } + ] + }, + "metadata": { "title": "Metadata" }, + "Executions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNodeExecution" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Executions" + } + }, + "type": "object", + "required": [ + "id", + "agentBlockId", + "agentGraphId", + "agentGraphVersion", + "constantInput", + "metadata" + ], + "title": "AgentNode", + "description": "Represents a AgentNode record" + }, + "AgentNodeExecution": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "agentGraphExecutionId": { + "type": "string", + "title": "Agentgraphexecutionid" + }, + "GraphExecution": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraphExecution" }, + { "type": "null" } + ] + }, + "agentNodeId": { "type": "string", "title": "Agentnodeid" }, + "Node": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentNode" }, + { "type": "null" } + ] + }, + "Input": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Input" + }, + "Output": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Output" + }, + "executionStatus": { + "$ref": "#/components/schemas/AgentExecutionStatus" + }, + "executionData": { + "anyOf": [{}, { "type": "null" }], + "title": "Executiondata" + }, + "addedTime": { + "type": "string", + "format": "date-time", + "title": "Addedtime" + }, + "queuedTime": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Queuedtime" + }, + "startedTime": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Startedtime" + }, + "endedTime": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Endedtime" + }, + "stats": { "anyOf": [{}, { "type": "null" }], "title": "Stats" } + }, + "type": "object", + "required": [ + "id", + "agentGraphExecutionId", + "agentNodeId", + "executionStatus", + "addedTime" + ], + "title": "AgentNodeExecution", + "description": "Represents a AgentNodeExecution record" + }, + "AgentNodeExecutionInputOutput": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "name": { "type": "string", "title": "Name" }, + "data": { "anyOf": [{}, { "type": "null" }], "title": "Data" }, + "time": { "type": "string", "format": "date-time", "title": "Time" }, + "referencedByInputExecId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Referencedbyinputexecid" + }, + "ReferencedByInputExec": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentNodeExecution" }, + { "type": "null" } + ] + }, + "referencedByOutputExecId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Referencedbyoutputexecid" + }, + "ReferencedByOutputExec": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentNodeExecution" }, + { "type": "null" } + ] + }, + "agentPresetId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Agentpresetid" + }, + "AgentPreset": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentPreset" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": ["id", "name", "time"], + "title": "AgentNodeExecutionInputOutput", + "description": "Represents a AgentNodeExecutionInputOutput record" + }, + "AgentNodeLink": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "agentNodeSourceId": { + "type": "string", + "title": "Agentnodesourceid" + }, + "AgentNodeSource": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentNode" }, + { "type": "null" } + ] + }, + "sourceName": { "type": "string", "title": "Sourcename" }, + "agentNodeSinkId": { "type": "string", "title": "Agentnodesinkid" }, + "AgentNodeSink": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentNode" }, + { "type": "null" } + ] + }, + "sinkName": { "type": "string", "title": "Sinkname" }, + "isStatic": { "type": "boolean", "title": "Isstatic" } + }, + "type": "object", + "required": [ + "id", + "agentNodeSourceId", + "sourceName", + "agentNodeSinkId", + "sinkName", + "isStatic" + ], + "title": "AgentNodeLink", + "description": "Represents a AgentNodeLink record" + }, + "AgentPreset": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "isActive": { "type": "boolean", "title": "Isactive" }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "InputPresets": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Inputpresets" + }, + "Executions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Executions" + }, + "webhookId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Webhookid" + }, + "Webhook": { + "anyOf": [ + { "$ref": "#/components/schemas/IntegrationWebhook" }, + { "type": "null" } + ] + }, + "isDeleted": { "type": "boolean", "title": "Isdeleted" } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "name", + "description", + "isActive", + "userId", + "agentGraphId", + "agentGraphVersion", + "isDeleted" + ], + "title": "AgentPreset", + "description": "Represents a AgentPreset record" + }, + "AnalyticsDetails": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "type": { "type": "string", "title": "Type" }, + "data": { "anyOf": [{}, { "type": "null" }], "title": "Data" }, + "dataIndex": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Dataindex" + } + }, + "type": "object", + "required": ["id", "createdAt", "updatedAt", "userId", "type"], + "title": "AnalyticsDetails", + "description": "Represents a AnalyticsDetails record" + }, + "AnalyticsMetrics": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "analyticMetric": { "type": "string", "title": "Analyticmetric" }, + "value": { "type": "number", "title": "Value" }, + "dataString": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Datastring" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "analyticMetric", + "value", + "userId" + ], + "title": "AnalyticsMetrics", + "description": "/////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////\n/////////// METRICS TRACKING TABLES ////////////////\n/////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////" + }, + "ApiResponse": { + "properties": { + "answer": { "type": "string", "title": "Answer" }, + "documents": { + "items": { "$ref": "#/components/schemas/Document" }, + "type": "array", + "title": "Documents" + }, + "success": { "type": "boolean", "title": "Success" } + }, + "type": "object", + "required": ["answer", "documents", "success"], + "title": "ApiResponse" + }, + "AutoTopUpConfig": { + "properties": { + "amount": { "type": "integer", "title": "Amount" }, + "threshold": { "type": "integer", "title": "Threshold" } + }, + "type": "object", + "required": ["amount", "threshold"], + "title": "AutoTopUpConfig" + }, + "AyrshareSSOResponse": { + "properties": { + "sso_url": { + "type": "string", + "title": "Sso Url", + "description": "The SSO URL for Ayrshare integration" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "title": "Expires At", + "description": "ISO timestamp when the URL expires" + } + }, + "type": "object", + "required": ["sso_url", "expires_at"], + "title": "AyrshareSSOResponse" + }, + "BaseGraph-Input": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" + }, + "recommended_schedule_cron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommended Schedule Cron" + }, + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + } + }, + "type": "object", + "required": ["name", "description"], + "title": "BaseGraph" + }, + "BaseGraph-Output": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" + }, + "recommended_schedule_cron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommended Schedule Cron" + }, + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema", + "readOnly": true + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema", + "readOnly": true + }, + "has_external_trigger": { + "type": "boolean", + "title": "Has External Trigger", + "readOnly": true + }, + "trigger_setup_info": { + "anyOf": [ + { "$ref": "#/components/schemas/GraphTriggerInfo" }, + { "type": "null" } + ], + "readOnly": true + } + }, + "type": "object", + "required": [ + "name", + "description", "input_schema", "output_schema", "has_external_trigger", @@ -5733,6 +6509,42 @@ "required": ["id", "provider", "type", "title", "scopes", "username"], "title": "CredentialsMetaResponse" }, + "CreditTransaction": { + "properties": { + "transactionKey": { "type": "string", "title": "Transactionkey" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "amount": { "type": "integer", "title": "Amount" }, + "type": { "$ref": "#/components/schemas/CreditTransactionType" }, + "runningBalance": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Runningbalance" + }, + "isActive": { "type": "boolean", "title": "Isactive" }, + "metadata": { "anyOf": [{}, { "type": "null" }], "title": "Metadata" } + }, + "type": "object", + "required": [ + "transactionKey", + "createdAt", + "userId", + "amount", + "type", + "isActive" + ], + "title": "CreditTransaction", + "description": "Represents a CreditTransaction record" + }, "CreditTransactionType": { "type": "string", "enum": ["TOP_UP", "USAGE", "GRANT", "REFUND", "CARD_CHECK"], @@ -6403,99 +7215,80 @@ "required": ["provider", "host"], "title": "HostScopedCredentials" }, - "LibraryAgent": { + "IntegrationWebhook": { "properties": { "id": { "type": "string", "title": "Id" }, - "graph_id": { "type": "string", "title": "Graph Id" }, - "graph_version": { "type": "integer", "title": "Graph Version" }, - "image_url": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Image Url" - }, - "creator_name": { "type": "string", "title": "Creator Name" }, - "creator_image_url": { - "type": "string", - "title": "Creator Image Url" - }, - "status": { "$ref": "#/components/schemas/LibraryAgentStatus" }, - "updated_at": { + "createdAt": { "type": "string", "format": "date-time", - "title": "Updated At" - }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" - }, - "input_schema": { - "additionalProperties": true, - "type": "object", - "title": "Input Schema" - }, - "output_schema": { - "additionalProperties": true, - "type": "object", - "title": "Output Schema" + "title": "Createdat" }, - "credentials_input_schema": { + "updatedAt": { "anyOf": [ - { "additionalProperties": true, "type": "object" }, + { "type": "string", "format": "date-time" }, { "type": "null" } ], - "title": "Credentials Input Schema", - "description": "Input schema for credentials required by the agent" - }, - "has_external_trigger": { - "type": "boolean", - "title": "Has External Trigger", - "description": "Whether the agent has an external trigger (e.g. webhook) node" + "title": "Updatedat" }, - "trigger_setup_info": { + "userId": { "type": "string", "title": "Userid" }, + "User": { "anyOf": [ - { "$ref": "#/components/schemas/GraphTriggerInfo" }, + { "$ref": "#/components/schemas/User" }, { "type": "null" } ] }, - "new_output": { "type": "boolean", "title": "New Output" }, - "can_access_graph": { - "type": "boolean", - "title": "Can Access Graph" + "provider": { "type": "string", "title": "Provider" }, + "credentialsId": { "type": "string", "title": "Credentialsid" }, + "webhookType": { "type": "string", "title": "Webhooktype" }, + "resource": { "type": "string", "title": "Resource" }, + "events": { + "items": { "type": "string" }, + "type": "array", + "title": "Events" }, - "is_latest_version": { - "type": "boolean", - "title": "Is Latest Version" + "config": { "title": "Config" }, + "secret": { "type": "string", "title": "Secret" }, + "providerWebhookId": { + "type": "string", + "title": "Providerwebhookid" + }, + "AgentNodes": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentNode" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Agentnodes" }, - "is_favorite": { "type": "boolean", "title": "Is Favorite" }, - "recommended_schedule_cron": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" + "AgentPresets": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentPreset" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Agentpresets" } }, "type": "object", "required": [ "id", - "graph_id", - "graph_version", - "image_url", - "creator_name", - "creator_image_url", - "status", - "updated_at", - "name", - "description", - "input_schema", - "output_schema", - "credentials_input_schema", - "has_external_trigger", - "new_output", - "can_access_graph", - "is_latest_version", - "is_favorite" + "createdAt", + "userId", + "provider", + "credentialsId", + "webhookType", + "resource", + "events", + "config", + "secret", + "providerWebhookId" ], - "title": "LibraryAgent", - "description": "Represents an agent in the library, including metadata for display and\nuser interaction within the system." + "title": "IntegrationWebhook", + "description": "Represents a IntegrationWebhook record" }, "LibraryAgentPreset": { "properties": { @@ -6674,7 +7467,9 @@ "LibraryAgentResponse": { "properties": { "agents": { - "items": { "$ref": "#/components/schemas/LibraryAgent" }, + "items": { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + }, "type": "array", "title": "Agents" }, @@ -6958,6 +7753,37 @@ "required": ["block_id", "graph_id", "graph_version"], "title": "NodeModel" }, + "NotificationEvent": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "UserNotificationBatch": { + "anyOf": [ + { "$ref": "#/components/schemas/UserNotificationBatch" }, + { "type": "null" } + ] + }, + "userNotificationBatchId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Usernotificationbatchid" + }, + "type": { "$ref": "#/components/schemas/NotificationType" }, + "data": { "title": "Data" } + }, + "type": "object", + "required": ["id", "createdAt", "updatedAt", "type", "data"], + "title": "NotificationEvent", + "description": "Represents a NotificationEvent record" + }, "NotificationPreference": { "properties": { "user_id": { "type": "string", "title": "User Id" }, @@ -7455,7 +8281,7 @@ ], "title": "PostmarkSubscriptionChangeWebhook" }, - "Profile": { + "Profile-Input": { "properties": { "name": { "type": "string", "title": "Name" }, "username": { "type": "string", "title": "Username" }, @@ -7476,6 +8302,69 @@ "required": ["name", "username", "description", "links", "avatar_url"], "title": "Profile" }, + "Profile-Output": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "userId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Userid" + }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "name": { "type": "string", "title": "Name" }, + "username": { "type": "string", "title": "Username" }, + "description": { "type": "string", "title": "Description" }, + "links": { + "items": { "type": "string" }, + "type": "array", + "title": "Links" + }, + "avatarUrl": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Avatarurl" + }, + "isFeatured": { "type": "boolean", "title": "Isfeatured" }, + "LibraryAgents": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/prisma__models__LibraryAgent" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Libraryagents" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "name", + "username", + "description", + "links", + "isFeatured" + ], + "title": "Profile", + "description": "Represents a Profile record" + }, "ProfileDetails": { "properties": { "name": { "type": "string", "title": "Name" }, @@ -7670,7 +8559,9 @@ "items": { "anyOf": [ { "$ref": "#/components/schemas/BlockInfo" }, - { "$ref": "#/components/schemas/LibraryAgent" }, + { + "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" + }, { "$ref": "#/components/schemas/StoreAgent" } ] }, @@ -7870,19 +8761,249 @@ "properties": { "store_listing_version_id": { "type": "string", - "title": "Store Listing Version Id" + "title": "Store Listing Version Id" + }, + "slug": { "type": "string", "title": "Slug" }, + "agent_name": { "type": "string", "title": "Agent Name" }, + "agent_video": { "type": "string", "title": "Agent Video" }, + "agent_image": { + "items": { "type": "string" }, + "type": "array", + "title": "Agent Image" + }, + "creator": { "type": "string", "title": "Creator" }, + "creator_avatar": { "type": "string", "title": "Creator Avatar" }, + "sub_heading": { "type": "string", "title": "Sub Heading" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" + }, + "categories": { + "items": { "type": "string" }, + "type": "array", + "title": "Categories" + }, + "runs": { "type": "integer", "title": "Runs" }, + "rating": { "type": "number", "title": "Rating" }, + "versions": { + "items": { "type": "string" }, + "type": "array", + "title": "Versions" + }, + "last_updated": { + "type": "string", + "format": "date-time", + "title": "Last Updated" + }, + "recommended_schedule_cron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommended Schedule Cron" + }, + "active_version_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Active Version Id" + }, + "has_approved_version": { + "type": "boolean", + "title": "Has Approved Version", + "default": false + } + }, + "type": "object", + "required": [ + "store_listing_version_id", + "slug", + "agent_name", + "agent_video", + "agent_image", + "creator", + "creator_avatar", + "sub_heading", + "description", + "categories", + "runs", + "rating", + "versions", + "last_updated" + ], + "title": "StoreAgentDetails" + }, + "StoreAgentsResponse": { + "properties": { + "agents": { + "items": { "$ref": "#/components/schemas/StoreAgent" }, + "type": "array", + "title": "Agents" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["agents", "pagination"], + "title": "StoreAgentsResponse" + }, + "StoreListing": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "isDeleted": { "type": "boolean", "title": "Isdeleted" }, + "hasApprovedVersion": { + "type": "boolean", + "title": "Hasapprovedversion" + }, + "slug": { "type": "string", "title": "Slug" }, + "useForOnboarding": { + "type": "boolean", + "title": "Useforonboarding" + }, + "activeVersionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Activeversionid" + }, + "ActiveVersion": { + "anyOf": [ + { "$ref": "#/components/schemas/StoreListingVersion" }, + { "type": "null" } + ] + }, + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "owningUserId": { "type": "string", "title": "Owninguserid" }, + "OwningUser": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "Versions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListingVersion" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Versions" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "isDeleted", + "hasApprovedVersion", + "slug", + "useForOnboarding", + "agentGraphId", + "agentGraphVersion", + "owningUserId" + ], + "title": "StoreListing", + "description": "Represents a StoreListing record" + }, + "StoreListingReview": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "storeListingVersionId": { + "type": "string", + "title": "Storelistingversionid" + }, + "StoreListingVersion": { + "anyOf": [ + { "$ref": "#/components/schemas/StoreListingVersion" }, + { "type": "null" } + ] + }, + "reviewByUserId": { "type": "string", "title": "Reviewbyuserid" }, + "ReviewByUser": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "score": { "type": "integer", "title": "Score" }, + "comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Comments" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "storeListingVersionId", + "reviewByUserId", + "score" + ], + "title": "StoreListingReview", + "description": "Represents a StoreListingReview record" + }, + "StoreListingVersion": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" }, - "slug": { "type": "string", "title": "Slug" }, - "agent_name": { "type": "string", "title": "Agent Name" }, - "agent_video": { "type": "string", "title": "Agent Video" }, - "agent_image": { + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "name": { "type": "string", "title": "Name" }, + "subHeading": { "type": "string", "title": "Subheading" }, + "videoUrl": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Videourl" + }, + "imageUrls": { "items": { "type": "string" }, "type": "array", - "title": "Agent Image" + "title": "Imageurls" }, - "creator": { "type": "string", "title": "Creator" }, - "creator_avatar": { "type": "string", "title": "Creator Avatar" }, - "sub_heading": { "type": "string", "title": "Sub Heading" }, "description": { "type": "string", "title": "Description" }, "instructions": { "anyOf": [{ "type": "string" }, { "type": "null" }], @@ -7893,63 +9014,97 @@ "type": "array", "title": "Categories" }, - "runs": { "type": "integer", "title": "Runs" }, - "rating": { "type": "number", "title": "Rating" }, - "versions": { - "items": { "type": "string" }, - "type": "array", - "title": "Versions" + "isFeatured": { "type": "boolean", "title": "Isfeatured" }, + "isDeleted": { "type": "boolean", "title": "Isdeleted" }, + "isAvailable": { "type": "boolean", "title": "Isavailable" }, + "submissionStatus": { + "$ref": "#/components/schemas/SubmissionStatus" }, - "last_updated": { - "type": "string", - "format": "date-time", - "title": "Last Updated" + "submittedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Submittedat" }, - "recommended_schedule_cron": { + "storeListingId": { "type": "string", "title": "Storelistingid" }, + "StoreListing": { + "anyOf": [ + { "$ref": "#/components/schemas/StoreListing" }, + { "type": "null" } + ] + }, + "ActiveFor": { + "anyOf": [ + { "$ref": "#/components/schemas/StoreListing" }, + { "type": "null" } + ] + }, + "changesSummary": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" + "title": "Changessummary" }, - "active_version_id": { + "reviewerId": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Active Version Id" + "title": "Reviewerid" }, - "has_approved_version": { - "type": "boolean", - "title": "Has Approved Version", - "default": false + "Reviewer": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "internalComments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Internalcomments" + }, + "reviewComments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Reviewcomments" + }, + "reviewedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Reviewedat" + }, + "recommendedScheduleCron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommendedschedulecron" + }, + "Reviews": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListingReview" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Reviews" } }, "type": "object", "required": [ - "store_listing_version_id", - "slug", - "agent_name", - "agent_video", - "agent_image", - "creator", - "creator_avatar", - "sub_heading", + "id", + "version", + "createdAt", + "updatedAt", + "agentGraphId", + "agentGraphVersion", + "name", + "subHeading", + "imageUrls", "description", "categories", - "runs", - "rating", - "versions", - "last_updated" + "isFeatured", + "isDeleted", + "isAvailable", + "submissionStatus", + "storeListingId" ], - "title": "StoreAgentDetails" - }, - "StoreAgentsResponse": { - "properties": { - "agents": { - "items": { "$ref": "#/components/schemas/StoreAgent" }, - "type": "array", - "title": "Agents" - }, - "pagination": { "$ref": "#/components/schemas/Pagination" } - }, - "type": "object", - "required": ["agents", "pagination"], - "title": "StoreAgentsResponse" + "title": "StoreListingVersion", + "description": "Represents a StoreListingVersion record" }, "StoreListingWithVersions": { "properties": { @@ -9589,31 +10744,305 @@ "WET", "Zulu" ], - "minLength": 1, - "title": "Timezone" + "minLength": 1, + "title": "Timezone" + } + }, + "type": "object", + "required": ["timezone"], + "title": "UpdateTimezoneRequest" + }, + "UploadFileResponse": { + "properties": { + "file_uri": { "type": "string", "title": "File Uri" }, + "file_name": { "type": "string", "title": "File Name" }, + "size": { "type": "integer", "title": "Size" }, + "content_type": { "type": "string", "title": "Content Type" }, + "expires_in_hours": { "type": "integer", "title": "Expires In Hours" } + }, + "type": "object", + "required": [ + "file_uri", + "file_name", + "size", + "content_type", + "expires_in_hours" + ], + "title": "UploadFileResponse" + }, + "User": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "email": { "type": "string", "title": "Email" }, + "emailVerified": { "type": "boolean", "title": "Emailverified" }, + "name": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Name" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "metadata": { "title": "Metadata" }, + "integrations": { "type": "string", "title": "Integrations" }, + "stripeCustomerId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Stripecustomerid" + }, + "topUpConfig": { + "anyOf": [{}, { "type": "null" }], + "title": "Topupconfig" + }, + "maxEmailsPerDay": { "type": "integer", "title": "Maxemailsperday" }, + "notifyOnAgentRun": { + "type": "boolean", + "title": "Notifyonagentrun" + }, + "notifyOnZeroBalance": { + "type": "boolean", + "title": "Notifyonzerobalance" + }, + "notifyOnLowBalance": { + "type": "boolean", + "title": "Notifyonlowbalance" + }, + "notifyOnBlockExecutionFailed": { + "type": "boolean", + "title": "Notifyonblockexecutionfailed" + }, + "notifyOnContinuousAgentError": { + "type": "boolean", + "title": "Notifyoncontinuousagenterror" + }, + "notifyOnDailySummary": { + "type": "boolean", + "title": "Notifyondailysummary" + }, + "notifyOnWeeklySummary": { + "type": "boolean", + "title": "Notifyonweeklysummary" + }, + "notifyOnMonthlySummary": { + "type": "boolean", + "title": "Notifyonmonthlysummary" + }, + "notifyOnAgentApproved": { + "type": "boolean", + "title": "Notifyonagentapproved" + }, + "notifyOnAgentRejected": { + "type": "boolean", + "title": "Notifyonagentrejected" + }, + "timezone": { "type": "string", "title": "Timezone" }, + "AgentGraphs": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentGraph" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Agentgraphs" + }, + "AgentGraphExecutions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Agentgraphexecutions" + }, + "AnalyticsDetails": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AnalyticsDetails" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Analyticsdetails" + }, + "AnalyticsMetrics": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AnalyticsMetrics" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Analyticsmetrics" + }, + "CreditTransactions": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/CreditTransaction" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Credittransactions" + }, + "UserBalance": { + "anyOf": [ + { "$ref": "#/components/schemas/UserBalance" }, + { "type": "null" } + ] + }, + "AgentPresets": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/AgentPreset" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Agentpresets" + }, + "LibraryAgents": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/prisma__models__LibraryAgent" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Libraryagents" + }, + "Profile": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/Profile-Output" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Profile" + }, + "UserOnboarding": { + "anyOf": [ + { "$ref": "#/components/schemas/UserOnboarding" }, + { "type": "null" } + ] + }, + "StoreListings": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListing" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Storelistings" + }, + "StoreListingReviews": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListingReview" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Storelistingreviews" + }, + "StoreVersionsReviewed": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/StoreListingVersion" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Storeversionsreviewed" + }, + "APIKeys": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/APIKey" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Apikeys" + }, + "IntegrationWebhooks": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/IntegrationWebhook" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Integrationwebhooks" + }, + "NotificationBatches": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/UserNotificationBatch" + }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Notificationbatches" } }, "type": "object", - "required": ["timezone"], - "title": "UpdateTimezoneRequest" + "required": [ + "id", + "email", + "emailVerified", + "createdAt", + "updatedAt", + "metadata", + "integrations", + "maxEmailsPerDay", + "notifyOnAgentRun", + "notifyOnZeroBalance", + "notifyOnLowBalance", + "notifyOnBlockExecutionFailed", + "notifyOnContinuousAgentError", + "notifyOnDailySummary", + "notifyOnWeeklySummary", + "notifyOnMonthlySummary", + "notifyOnAgentApproved", + "notifyOnAgentRejected", + "timezone" + ], + "title": "User", + "description": "Represents a User record" }, - "UploadFileResponse": { + "UserBalance": { "properties": { - "file_uri": { "type": "string", "title": "File Uri" }, - "file_name": { "type": "string", "title": "File Name" }, - "size": { "type": "integer", "title": "Size" }, - "content_type": { "type": "string", "title": "Content Type" }, - "expires_in_hours": { "type": "integer", "title": "Expires In Hours" } + "userId": { "type": "string", "title": "Userid" }, + "balance": { "type": "integer", "title": "Balance" }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "user": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + } }, "type": "object", - "required": [ - "file_uri", - "file_name", - "size", - "content_type", - "expires_in_hours" - ], - "title": "UploadFileResponse" + "required": ["userId", "balance", "updatedAt"], + "title": "UserBalance", + "description": "Represents a UserBalance record" }, "UserHistoryResponse": { "properties": { @@ -9629,6 +11058,135 @@ "title": "UserHistoryResponse", "description": "Response model for listings with version history" }, + "UserNotificationBatch": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "type": { "$ref": "#/components/schemas/NotificationType" }, + "Notifications": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/NotificationEvent" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Notifications" + } + }, + "type": "object", + "required": ["id", "createdAt", "updatedAt", "userId", "type"], + "title": "UserNotificationBatch", + "description": "Represents a UserNotificationBatch record" + }, + "UserOnboarding": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Updatedat" + }, + "completedSteps": { + "items": { "$ref": "#/components/schemas/OnboardingStep" }, + "type": "array", + "title": "Completedsteps" + }, + "walletShown": { "type": "boolean", "title": "Walletshown" }, + "notified": { + "items": { "$ref": "#/components/schemas/OnboardingStep" }, + "type": "array", + "title": "Notified" + }, + "rewardedFor": { + "items": { "$ref": "#/components/schemas/OnboardingStep" }, + "type": "array", + "title": "Rewardedfor" + }, + "usageReason": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Usagereason" + }, + "integrations": { + "items": { "type": "string" }, + "type": "array", + "title": "Integrations" + }, + "otherIntegrations": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Otherintegrations" + }, + "selectedStoreListingVersionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Selectedstorelistingversionid" + }, + "agentInput": { + "anyOf": [{}, { "type": "null" }], + "title": "Agentinput" + }, + "onboardingAgentExecutionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Onboardingagentexecutionid" + }, + "agentRuns": { "type": "integer", "title": "Agentruns" }, + "lastRunAt": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Lastrunat" + }, + "consecutiveRunDays": { + "type": "integer", + "title": "Consecutiverundays" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "completedSteps", + "walletShown", + "notified", + "rewardedFor", + "integrations", + "agentRuns", + "consecutiveRunDays", + "userId" + ], + "title": "UserOnboarding", + "description": "Represents a UserOnboarding record" + }, "UserOnboardingUpdate": { "properties": { "completedSteps": { @@ -9864,6 +11422,171 @@ "url" ], "title": "Webhook" + }, + "backend__server__v2__library__model__LibraryAgent": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "image_url": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Image Url" + }, + "creator_name": { "type": "string", "title": "Creator Name" }, + "creator_image_url": { + "type": "string", + "title": "Creator Image Url" + }, + "status": { "$ref": "#/components/schemas/LibraryAgentStatus" }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" + }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema" + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema" + }, + "credentials_input_schema": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Credentials Input Schema", + "description": "Input schema for credentials required by the agent" + }, + "has_external_trigger": { + "type": "boolean", + "title": "Has External Trigger", + "description": "Whether the agent has an external trigger (e.g. webhook) node" + }, + "trigger_setup_info": { + "anyOf": [ + { "$ref": "#/components/schemas/GraphTriggerInfo" }, + { "type": "null" } + ] + }, + "new_output": { "type": "boolean", "title": "New Output" }, + "can_access_graph": { + "type": "boolean", + "title": "Can Access Graph" + }, + "is_latest_version": { + "type": "boolean", + "title": "Is Latest Version" + }, + "is_favorite": { "type": "boolean", "title": "Is Favorite" }, + "recommended_schedule_cron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommended Schedule Cron" + } + }, + "type": "object", + "required": [ + "id", + "graph_id", + "graph_version", + "image_url", + "creator_name", + "creator_image_url", + "status", + "updated_at", + "name", + "description", + "input_schema", + "output_schema", + "credentials_input_schema", + "has_external_trigger", + "new_output", + "can_access_graph", + "is_latest_version", + "is_favorite" + ], + "title": "LibraryAgent", + "description": "Represents an agent in the library, including metadata for display and\nuser interaction within the system." + }, + "prisma__models__LibraryAgent": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat" + }, + "userId": { "type": "string", "title": "Userid" }, + "User": { + "anyOf": [ + { "$ref": "#/components/schemas/User" }, + { "type": "null" } + ] + }, + "imageUrl": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Imageurl" + }, + "agentGraphId": { "type": "string", "title": "Agentgraphid" }, + "agentGraphVersion": { + "type": "integer", + "title": "Agentgraphversion" + }, + "AgentGraph": { + "anyOf": [ + { "$ref": "#/components/schemas/AgentGraph" }, + { "type": "null" } + ] + }, + "creatorId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Creatorid" + }, + "Creator": { + "anyOf": [ + { "$ref": "#/components/schemas/Profile-Output" }, + { "type": "null" } + ] + }, + "useGraphIsActiveVersion": { + "type": "boolean", + "title": "Usegraphisactiveversion" + }, + "isFavorite": { "type": "boolean", "title": "Isfavorite" }, + "isCreatedByUser": { "type": "boolean", "title": "Iscreatedbyuser" }, + "isArchived": { "type": "boolean", "title": "Isarchived" }, + "isDeleted": { "type": "boolean", "title": "Isdeleted" } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt", + "userId", + "agentGraphId", + "agentGraphVersion", + "useGraphIsActiveVersion", + "isFavorite", + "isCreatedByUser", + "isArchived", + "isDeleted" + ], + "title": "LibraryAgent", + "description": "Represents a LibraryAgent record" } }, "securitySchemes": { From 3d48e49bbcef4b082cc1a5859a27e995dec0f2ea Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 17:07:16 +0900 Subject: [PATCH 10/33] Backend `SCHEDULE_AGENT` --- autogpt_platform/backend/backend/server/routers/v1.py | 2 ++ .../AgentRunsView/components/RunAgentModal/useAgentRunModal.ts | 2 -- .../OldAgentLibraryView/components/cron-scheduler-dialog.tsx | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 24c4fd9b20a6..ddc0ce751f86 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -1217,6 +1217,8 @@ async def create_graph_execution_schedule( result.next_run_time, user_timezone ) + await complete_onboarding_step(user_id, OnboardingStep.SCHEDULE_AGENT) + return result diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts index 2e8fc02d9778..641f53c8225a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts @@ -331,8 +331,6 @@ export function useAgentRunModal( userTimezone && userTimezone !== "not-set" ? userTimezone : undefined, }, }); - - completeOnboardingStep("SCHEDULE_AGENT"); }, [ allRequiredInputsAreSet, scheduleName, diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx index a587d11aff1d..6b5124113188 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx @@ -94,7 +94,6 @@ export function CronSchedulerDialog(props: CronSchedulerDialogProps) { props.onSubmit(cronExpression); } setOpen(false); - completeStep("SCHEDULE_AGENT"); }; return ( From 0560e3b43a5dc35a59d11f1598eea15ce3d2e9f8 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 17:07:51 +0900 Subject: [PATCH 11/33] Add `resolveResponse` helper function --- .../frontend/src/app/api/helpers.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/autogpt_platform/frontend/src/app/api/helpers.ts b/autogpt_platform/frontend/src/app/api/helpers.ts index 30bca0e1f088..7599722cd514 100644 --- a/autogpt_platform/frontend/src/app/api/helpers.ts +++ b/autogpt_platform/frontend/src/app/api/helpers.ts @@ -26,6 +26,67 @@ export function okData(res: unknown): T | undefined { return (res as { data: T }).data; } +type ResponseWithData = { status: number; data: unknown }; +type ExtractResponseData = T extends { + data: infer D; +} +? D +: never; +type SuccessfulResponses = T extends { + status: infer S; +} +? S extends number +? `${S}` extends `2${string}` +? T +: never +: never +: never; + +/** + * Resolve an Orval response to its payload after asserting the status is either the explicit + * `expected` code or any other 2xx status if `expected` is omitted. + * + * Usage with server actions: + * ```ts + * const onboarding = await expectStatus(getV1OnboardingState()); + * const agent = await expectStatus( + * postV2AddMarketplaceAgent({ store_listing_version_id }), + * 201, + * ); + * ``` + */ +export function resolveResponse< + TSuccess extends ResponseWithData, + TCode extends number, +>( + promise: Promise, + expected: TCode, +): Promise>>; +export function resolveResponse( + promise: Promise, +): Promise>>; +export async function resolveResponse< + TSuccess extends ResponseWithData, + TCode extends number, +>( + promise: Promise, + expected?: TCode, +) { + const res = await promise; + const isSuccessfulStatus = + typeof res.status === "number" && res.status >= 200 && res.status < 300; + + if (typeof expected === "number") { + if (res.status !== expected) { + throw new Error(`Unexpected status ${res.status}`); + } + } else if (!isSuccessfulStatus) { + throw new Error(`Unexpected status ${res.status}`); + } + + return res.data; +} + export async function shouldShowOnboarding() { const api = new BackendAPI(); const isEnabled = await api.isOnboardingEnabled(); From 44778644d55f193c97c19e3dc50b14286624792e Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 17:21:00 +0900 Subject: [PATCH 12/33] Use `postV2AddMarketplaceAgent` --- .../onboarding/5-run/useOnboardingRunStep.tsx | 11 +++++++---- .../app/(no-navbar)/onboarding/6-congrats/actions.ts | 10 +++++++--- .../frontend/src/lib/autogpt-server-api/client.ts | 8 -------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx index ce34036702e6..26c5fc4c24f0 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx @@ -12,6 +12,9 @@ import { useGetV2GetAgentByVersion, useGetV2GetAgentGraph, } from "@/app/api/__generated__/endpoints/store/store"; +import { resolveResponse } from "@/app/api/helpers"; +import { postV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library"; +import { GraphID } from "@/lib/autogpt-server-api"; export function useOnboardingRunStep() { const onboarding = useOnboarding(undefined, "AGENT_CHOICE"); @@ -111,12 +114,12 @@ export function useOnboardingRunStep() { setRunningAgent(true); try { - const libraryAgent = await api.addMarketplaceAgentToLibrary( - storeAgent?.store_listing_version_id || "", - ); + const libraryAgent = await resolveResponse(postV2AddMarketplaceAgent({ + store_listing_version_id: storeAgent?.store_listing_version_id || "", + })); const { id: runID } = await api.executeGraph( - libraryAgent.graph_id, + libraryAgent.graph_id as GraphID, libraryAgent.graph_version, onboarding.state.agentInput || {}, inputCredentials, diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts index 202bad57bdc2..7da77fe207af 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts @@ -1,5 +1,7 @@ "use server"; import BackendAPI from "@/lib/autogpt-server-api"; +import { postV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library"; +import { resolveResponse } from "@/app/api/helpers"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; @@ -8,9 +10,11 @@ export async function finishOnboarding() { const onboarding = await api.getUserOnboarding(); const listingId = onboarding?.selectedStoreListingVersionId; if (listingId) { - const libraryAgent = await api.addMarketplaceAgentToLibrary(listingId); - revalidatePath(`/library/agents/${libraryAgent.id}`, "layout"); - redirect(`/library/agents/${libraryAgent.id}`); + const data = await resolveResponse(postV2AddMarketplaceAgent({ + store_listing_version_id: listingId, + })); + revalidatePath(`/library/agents/${data.id}`, "layout"); + redirect(`/library/agents/${data.id}`); } else { revalidatePath("/library", "layout"); redirect("/library"); diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index bec9c1749cef..5ab62c0b4444 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -691,14 +691,6 @@ export default class BackendAPI { }); } - addMarketplaceAgentToLibrary( - storeListingVersionID: string, - ): Promise { - return this._request("POST", "/library/agents", { - store_listing_version_id: storeListingVersionID, - }); - } - updateLibraryAgent( libraryAgentId: LibraryAgentID, params: { From 613940b2a8c9ea11a978e56fd070ab86249a776f Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 17:21:31 +0900 Subject: [PATCH 13/33] Use `getV1OnboardingState` --- .../onboarding/6-congrats/actions.ts | 7 ++--- .../src/app/(no-navbar)/onboarding/page.tsx | 7 +++-- .../frontend/src/app/api/helpers.ts | 3 ++- .../src/lib/autogpt-server-api/client.ts | 4 --- .../src/providers/onboarding/helpers.ts | 26 +++++++++++-------- .../onboarding/onboarding-provider.tsx | 6 ++--- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts index 7da77fe207af..a77f6a0ee255 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts @@ -1,13 +1,14 @@ "use server"; -import BackendAPI from "@/lib/autogpt-server-api"; + import { postV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library"; +import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; import { resolveResponse } from "@/app/api/helpers"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; export async function finishOnboarding() { - const api = new BackendAPI(); - const onboarding = await api.getUserOnboarding(); + const onboarding = await resolveResponse(getV1OnboardingState()); + const listingId = onboarding?.selectedStoreListingVersionId; if (listingId) { const data = await resolveResponse(postV2AddMarketplaceAgent({ diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx index c1e3bf05404f..49c13bafe966 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx @@ -1,20 +1,19 @@ -import BackendAPI from "@/lib/autogpt-server-api"; import { redirect } from "next/navigation"; import { finishOnboarding } from "./6-congrats/actions"; -import { shouldShowOnboarding } from "@/app/api/helpers"; +import { resolveResponse, shouldShowOnboarding } from "@/app/api/helpers"; +import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; // Force dynamic rendering to avoid static generation issues with cookies export const dynamic = "force-dynamic"; export default async function OnboardingPage() { - const api = new BackendAPI(); const isOnboardingEnabled = await shouldShowOnboarding(); if (!isOnboardingEnabled) { redirect("/marketplace"); } - const onboarding = await api.getUserOnboarding(); + const onboarding = await resolveResponse(getV1OnboardingState()); // CONGRATS is the last step in intro onboarding if (onboarding.completedSteps.includes("GET_RESULTS")) diff --git a/autogpt_platform/frontend/src/app/api/helpers.ts b/autogpt_platform/frontend/src/app/api/helpers.ts index 7599722cd514..3e6009a298cb 100644 --- a/autogpt_platform/frontend/src/app/api/helpers.ts +++ b/autogpt_platform/frontend/src/app/api/helpers.ts @@ -1,4 +1,5 @@ import BackendAPI from "@/lib/autogpt-server-api"; +import { getV1OnboardingState } from "./__generated__/endpoints/onboarding/onboarding"; /** * Narrow an orval response to its success payload if and only if it is a `200` status with OK shape. @@ -90,7 +91,7 @@ export async function resolveResponse< export async function shouldShowOnboarding() { const api = new BackendAPI(); const isEnabled = await api.isOnboardingEnabled(); - const onboarding = await api.getUserOnboarding(); + const onboarding = await resolveResponse(getV1OnboardingState()); const isCompleted = onboarding.completedSteps.includes("CONGRATS"); return isEnabled && !isCompleted; } diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 5ab62c0b4444..4dfed42e1d1d 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -199,10 +199,6 @@ export default class BackendAPI { ////////////// ONBOARDING ////////////// //////////////////////////////////////// - getUserOnboarding(): Promise { - return this._get("/onboarding"); - } - updateUserOnboarding( onboarding: Omit, "rewardedFor">, ): Promise { diff --git a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts index 34d7e6f5d059..bf16463d7eba 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts +++ b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts @@ -1,4 +1,5 @@ -import { OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api"; +import { GraphExecutionID, OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api"; +import { UserOnboarding as RawUserOnboarding } from "@/app/api/__generated__/models/userOnboarding"; export function isToday(date: Date): boolean { const today = new Date(); @@ -61,19 +62,22 @@ export function getRunMilestoneSteps( } export function processOnboardingData( - onboarding: UserOnboarding, + onboarding: RawUserOnboarding, ): UserOnboarding { - // Patch for TRIGGER_WEBHOOK - only set on backend then overwritten by frontend - const completeWebhook = - onboarding.rewardedFor.includes("TRIGGER_WEBHOOK") && - !onboarding.completedSteps.includes("TRIGGER_WEBHOOK") - ? (["TRIGGER_WEBHOOK"] as OnboardingStep[]) - : []; - return { - ...onboarding, - completedSteps: [...completeWebhook, ...onboarding.completedSteps], + completedSteps: onboarding.completedSteps, + walletShown: onboarding.walletShown, + notified: onboarding.notified, + rewardedFor: onboarding.rewardedFor, + usageReason: onboarding.usageReason || null, + integrations: onboarding.integrations, + otherIntegrations: onboarding.otherIntegrations || null, + selectedStoreListingVersionId: onboarding.selectedStoreListingVersionId || null, + agentInput: onboarding.agentInput as {} || null, + onboardingAgentExecutionId: onboarding.onboardingAgentExecutionId as GraphExecutionID || null, lastRunAt: onboarding.lastRunAt ? new Date(onboarding.lastRunAt) : null, + consecutiveRunDays: onboarding.consecutiveRunDays, + agentRuns: onboarding.agentRuns, }; } diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index 2505664bff12..8611b7e34047 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -31,6 +31,8 @@ import { processOnboardingData, shouldRedirectFromOnboarding, } from "./helpers"; +import { resolveResponse } from "@/app/api/helpers"; +import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; const OnboardingContext = createContext< | { @@ -132,9 +134,7 @@ export default function OnboardingProvider({ } } - const onboarding = await api.getUserOnboarding(); - if (!onboarding) return; - + const onboarding = await resolveResponse(getV1OnboardingState()); const processedOnboarding = processOnboardingData(onboarding); setState(processedOnboarding); From e7915ea54670e514cc5713a5cad80e5412c44ee4 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Sun, 9 Nov 2025 18:14:23 +0900 Subject: [PATCH 14/33] Use `patchV1UpdateOnboardingState` --- .../backend/backend/data/onboarding.py | 33 +++++-------------- .../RunAgentModal/useAgentRunModal.ts | 2 -- .../frontend/src/app/api/openapi.json | 25 -------------- .../src/lib/autogpt-server-api/client.ts | 6 ---- .../onboarding/onboarding-provider.tsx | 4 +-- 5 files changed, 10 insertions(+), 60 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 7cf8fbcb4f40..9b833fb02e9e 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -33,7 +33,6 @@ class UserOnboardingUpdate(pydantic.BaseModel): - completedSteps: Optional[list[OnboardingStep]] = None walletShown: Optional[bool] = None notified: Optional[list[OnboardingStep]] = None usageReason: Optional[str] = None @@ -42,9 +41,6 @@ class UserOnboardingUpdate(pydantic.BaseModel): selectedStoreListingVersionId: Optional[str] = None agentInput: Optional[dict[str, Any]] = None onboardingAgentExecutionId: Optional[str] = None - agentRuns: Optional[int] = None - lastRunAt: Optional[datetime] = None - consecutiveRunDays: Optional[int] = None async def get_user_onboarding(user_id: str): @@ -83,14 +79,6 @@ async def reset_user_onboarding(user_id: str): async def update_user_onboarding(user_id: str, data: UserOnboardingUpdate): update: UserOnboardingUpdateInput = {} onboarding = await get_user_onboarding(user_id) - if data.completedSteps is not None: - update["completedSteps"] = list( - set(data.completedSteps + onboarding.completedSteps) - ) - for step in data.completedSteps: - if step not in onboarding.completedSteps: - await _reward_user(user_id, onboarding, step) - await _send_onboarding_notification(user_id, step) if data.walletShown: update["walletShown"] = data.walletShown if data.notified is not None: @@ -107,12 +95,6 @@ async def update_user_onboarding(user_id: str, data: UserOnboardingUpdate): update["agentInput"] = SafeJson(data.agentInput) if data.onboardingAgentExecutionId is not None: update["onboardingAgentExecutionId"] = data.onboardingAgentExecutionId - if data.agentRuns is not None and data.agentRuns > onboarding.agentRuns: - update["agentRuns"] = data.agentRuns - if data.lastRunAt is not None: - update["lastRunAt"] = data.lastRunAt - if data.consecutiveRunDays is not None: - update["consecutiveRunDays"] = data.consecutiveRunDays return await UserOnboarding.prisma().upsert( where={"userId": user_id}, @@ -161,14 +143,12 @@ async def _reward_user(user_id: str, onboarding: UserOnboarding, step: Onboardin if step in onboarding.rewardedFor: return - onboarding.rewardedFor.append(step) user_credit_model = await get_user_credit_model(user_id) await user_credit_model.onboarding_reward(user_id, reward, step) await UserOnboarding.prisma().update( where={"userId": user_id}, data={ - "completedSteps": list(set(onboarding.completedSteps + [step])), - "rewardedFor": onboarding.rewardedFor, + "rewardedFor": list(set(onboarding.rewardedFor + [step])), }, ) @@ -177,13 +157,16 @@ async def complete_onboarding_step(user_id: str, step: OnboardingStep): """ Completes the specified onboarding step for the user if not already completed. """ - onboarding = await get_user_onboarding(user_id) if step not in onboarding.completedSteps: - await update_user_onboarding( - user_id, - UserOnboardingUpdate(completedSteps=onboarding.completedSteps + [step]), + onboarding = await get_user_onboarding(user_id) + await UserOnboarding.prisma().update( + where={"userId": user_id}, + data={ + "completedSteps": list(set(onboarding.completedSteps + [step])), + }, ) + await _reward_user(user_id, onboarding, step) await _send_onboarding_notification(user_id, step) diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts index 641f53c8225a..7a346d8d7b94 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts @@ -16,7 +16,6 @@ import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutio import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo"; import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; import { analytics } from "@/services/analytics"; export type RunVariant = @@ -50,7 +49,6 @@ export function useAgentRunModal( const [cronExpression, setCronExpression] = useState( agent.recommended_schedule_cron || "0 9 * * 1", ); - const { completeStep: completeOnboardingStep } = useOnboarding(); // Get user timezone for scheduling const { data: userTimezone } = useGetV1GetUserTimezone({ diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index 50722d32c6c9..d452a8b8b97b 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -11189,16 +11189,6 @@ }, "UserOnboardingUpdate": { "properties": { - "completedSteps": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/OnboardingStep" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Completedsteps" - }, "walletShown": { "anyOf": [{ "type": "boolean" }, { "type": "null" }], "title": "Walletshown" @@ -11242,21 +11232,6 @@ "onboardingAgentExecutionId": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Onboardingagentexecutionid" - }, - "agentRuns": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Agentruns" - }, - "lastRunAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Lastrunat" - }, - "consecutiveRunDays": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Consecutiverundays" } }, "type": "object", diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 4dfed42e1d1d..c08403127b0c 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -199,12 +199,6 @@ export default class BackendAPI { ////////////// ONBOARDING ////////////// //////////////////////////////////////// - updateUserOnboarding( - onboarding: Omit, "rewardedFor">, - ): Promise { - return this._request("PATCH", "/onboarding", onboarding); - } - getOnboardingAgents(): Promise { return this._get("/onboarding/agents"); } diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index 8611b7e34047..3334d51001a6 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -32,7 +32,7 @@ import { shouldRedirectFromOnboarding, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; -import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { getV1OnboardingState, patchV1UpdateOnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; const OnboardingContext = createContext< | { @@ -176,7 +176,7 @@ export default function OnboardingProvider({ const updatePromise = (async () => { try { if (!isMounted.current) return; - await api.updateUserOnboarding(newState); + await patchV1UpdateOnboardingState(newState); } catch (error) { if (isMounted.current) { console.error("Failed to update user onboarding:", error); From 55b55f9cf56fbcb7316cd67bf553bb8d923acaf9 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 10 Nov 2025 18:19:43 +0900 Subject: [PATCH 15/33] Use `postV1CompleteOnboardingStep` --- .../backend/backend/data/onboarding.py | 25 +++++++- .../backend/backend/server/routers/v1.py | 17 ++++- .../frontend/src/app/api/openapi.json | 57 +++++++++++++++++ .../Navbar/components/Wallet/Wallet.tsx | 3 +- .../src/providers/onboarding/helpers.ts | 34 +++++----- .../onboarding/onboarding-provider.tsx | 62 ++++++++++--------- 6 files changed, 149 insertions(+), 49 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 9b833fb02e9e..70024e1b856e 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -1,6 +1,5 @@ import re -from datetime import datetime -from typing import Any, Optional +from typing import Any, Literal, Optional import prisma import pydantic @@ -31,6 +30,28 @@ POINTS_AGENT_COUNT = 50 # Number of agents to calculate points for MIN_AGENT_COUNT = 2 # Minimum number of marketplace agents to enable onboarding +FrontendOnboardingStep = Literal[ + OnboardingStep.WELCOME, + OnboardingStep.USAGE_REASON, + OnboardingStep.INTEGRATIONS, + OnboardingStep.AGENT_CHOICE, + OnboardingStep.AGENT_NEW_RUN, + OnboardingStep.AGENT_INPUT, + OnboardingStep.CONGRATS, + OnboardingStep.GET_RESULTS, + OnboardingStep.MARKETPLACE_VISIT, + OnboardingStep.MARKETPLACE_ADD_AGENT, + OnboardingStep.MARKETPLACE_RUN_AGENT, + OnboardingStep.BUILDER_SAVE_AGENT, + OnboardingStep.RE_RUN_AGENT, + OnboardingStep.RUN_AGENTS, + OnboardingStep.RUN_3_DAYS, + OnboardingStep.RUN_14_DAYS, + OnboardingStep.RUN_AGENTS_100, + OnboardingStep.BUILDER_OPEN, + OnboardingStep.BUILDER_RUN_AGENT, +] + class UserOnboardingUpdate(pydantic.BaseModel): walletShown: Optional[bool] = None diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index ddc0ce751f86..40017805f9aa 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -5,7 +5,7 @@ import uuid from collections import defaultdict from datetime import datetime, timezone -from typing import Annotated, Any, Sequence +from typing import Annotated, Any, Sequence, get_args import pydantic import stripe @@ -50,6 +50,7 @@ from backend.data.model import CredentialsMetaInput from backend.data.notifications import NotificationPreference, NotificationPreferenceDTO from backend.data.onboarding import ( + FrontendOnboardingStep, UserOnboardingUpdate, OnboardingStep, complete_onboarding_step, @@ -242,6 +243,20 @@ async def update_onboarding( return await update_user_onboarding(user_id, data) +@v1_router.post( + "/onboarding/step", + summary="Complete onboarding step", + tags=["onboarding"], + dependencies=[Security(requires_user)], +) +async def onboarding_complete_step( + user_id: Annotated[str, Security(get_user_id)], step: FrontendOnboardingStep +): + if step not in get_args(FrontendOnboardingStep): + raise HTTPException(status_code=400, detail="Invalid onboarding step") + return await complete_onboarding_step(user_id, step) + + @v1_router.get( "/onboarding/agents", summary="Recommended onboarding agents", diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index d452a8b8b97b..b246040ed9c9 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -880,6 +880,63 @@ "security": [{ "HTTPBearerJWT": [] }] } }, + "/api/onboarding/step": { + "post": { + "tags": ["v1", "onboarding"], + "summary": "Complete onboarding step", + "operationId": "postV1Complete onboarding step", + "security": [{ "HTTPBearerJWT": [] }], + "parameters": [ + { + "name": "step", + "in": "query", + "required": true, + "schema": { + "enum": [ + "WELCOME", + "USAGE_REASON", + "INTEGRATIONS", + "AGENT_CHOICE", + "AGENT_NEW_RUN", + "AGENT_INPUT", + "CONGRATS", + "GET_RESULTS", + "MARKETPLACE_VISIT", + "MARKETPLACE_ADD_AGENT", + "MARKETPLACE_RUN_AGENT", + "BUILDER_SAVE_AGENT", + "RE_RUN_AGENT", + "RUN_AGENTS", + "RUN_3_DAYS", + "RUN_14_DAYS", + "RUN_AGENTS_100", + "BUILDER_OPEN", + "BUILDER_RUN_AGENT" + ], + "type": "string", + "title": "Step" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + }, + "401": { + "$ref": "#/components/responses/HTTP401NotAuthenticatedError" + } + } + } + }, "/api/onboarding/agents": { "get": { "tags": ["v1", "onboarding"], diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx index 4e7d58e9242b..3146d961c82e 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx @@ -44,7 +44,7 @@ export interface TaskGroup { } export function Wallet() { - const { state, updateState } = useOnboarding(); + const { state, updateState, fetchOnboarding } = useOnboarding(); const isPaymentEnabled = useGetFlag(Flag.ENABLE_PLATFORM_PAYMENT); const groups = useMemo(() => { @@ -264,6 +264,7 @@ export function Wallet() { return; } fetchCredits(); + fetchOnboarding(); party.confetti(walletRef.current!, { count: 30, spread: 120, diff --git a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts index bf16463d7eba..e3bf3c6d74cc 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts +++ b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts @@ -1,5 +1,6 @@ import { GraphExecutionID, OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api"; import { UserOnboarding as RawUserOnboarding } from "@/app/api/__generated__/models/userOnboarding"; +import { UserOnboardingUpdate } from "@/app/api/__generated__/models/userOnboardingUpdate"; export function isToday(date: Date): boolean { const today = new Date(); @@ -91,23 +92,24 @@ export function shouldRedirectFromOnboarding( ); } -export function createInitialOnboardingState( - newState: Omit, "rewardedFor">, + +export function updateOnboardingState( + prevState: UserOnboarding | null, + newState: UserOnboardingUpdate, ): UserOnboarding { return { - completedSteps: [], - walletShown: true, - notified: [], - rewardedFor: [], - usageReason: null, - integrations: [], - otherIntegrations: null, - selectedStoreListingVersionId: null, - agentInput: null, - onboardingAgentExecutionId: null, - agentRuns: 0, - lastRunAt: null, - consecutiveRunDays: 0, - ...newState, + completedSteps: prevState?.completedSteps || [], + walletShown: newState.walletShown || prevState?.walletShown || true, + notified: newState.notified || prevState?.notified || [], + rewardedFor: prevState?.rewardedFor || [], + usageReason: newState.usageReason || prevState?.usageReason || null, + integrations: newState.integrations || prevState?.integrations || [], + otherIntegrations: newState.otherIntegrations || prevState?.otherIntegrations || null, + selectedStoreListingVersionId: newState.selectedStoreListingVersionId || prevState?.selectedStoreListingVersionId || null, + agentInput: newState.agentInput as Record || prevState?.agentInput || null, + onboardingAgentExecutionId: newState.onboardingAgentExecutionId as GraphExecutionID || prevState?.onboardingAgentExecutionId || null, + agentRuns: prevState?.agentRuns || 0, + lastRunAt: prevState?.lastRunAt || null, + consecutiveRunDays: prevState?.consecutiveRunDays || 0, }; } diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index 3334d51001a6..1d72a02bf3ca 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -26,13 +26,17 @@ import { } from "react"; import { calculateConsecutiveDays, - createInitialOnboardingState, + updateOnboardingState, getRunMilestoneSteps, processOnboardingData, shouldRedirectFromOnboarding, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; -import { getV1OnboardingState, patchV1UpdateOnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { UserOnboardingUpdate } from "@/app/api/__generated__/models/userOnboardingUpdate"; +import { PostV1CompleteOnboardingStepStep } from "@/app/api/__generated__/models/postV1CompleteOnboardingStepStep"; + +type FrontendOnboardingStep = PostV1CompleteOnboardingStepStep; const OnboardingContext = createContext< | { @@ -42,13 +46,14 @@ const OnboardingContext = createContext< ) => void; step: number; setStep: (step: number) => void; - completeStep: (step: OnboardingStep) => void; + completeStep: (step: FrontendOnboardingStep) => void; incrementRuns: () => void; + fetchOnboarding: () => Promise; } | undefined >(undefined); -export function useOnboarding(step?: number, completeStep?: OnboardingStep) { +export function useOnboarding(step?: number, completeStep?: FrontendOnboardingStep) { const context = useContext(OnboardingContext); if (!context) @@ -115,6 +120,13 @@ export default function OnboardingProvider({ const isOnOnboardingRoute = pathname.startsWith("/onboarding"); + const fetchOnboarding = useCallback(async () => { + const onboarding = await resolveResponse(getV1OnboardingState()); + const processedOnboarding = processOnboardingData(onboarding); + setState(processedOnboarding); + return processedOnboarding; + }, []); + useEffect(() => { // Prevent multiple initializations if (hasInitialized.current || isUserLoading || !user) { @@ -134,15 +146,13 @@ export default function OnboardingProvider({ } } - const onboarding = await resolveResponse(getV1OnboardingState()); - const processedOnboarding = processOnboardingData(onboarding); - setState(processedOnboarding); + const onboarding = await fetchOnboarding(); // Handle redirects for completed onboarding if ( isOnOnboardingRoute && shouldRedirectFromOnboarding( - processedOnboarding.completedSteps, + onboarding.completedSteps, pathname, ) ) { @@ -161,17 +171,12 @@ export default function OnboardingProvider({ } initializeOnboarding(); - }, [api, isOnOnboardingRoute, router, user, isUserLoading, pathname]); + }, [api, isOnOnboardingRoute, router, user, isUserLoading, pathname, fetchOnboarding, toast]); const updateState = useCallback( - (newState: Omit, "rewardedFor">) => { + (newState: UserOnboardingUpdate) => { // Update local state immediately - setState((prev) => { - if (!prev) { - return createInitialOnboardingState(newState); - } - return { ...prev, ...newState }; - }); + setState((prev) => updateOnboardingState(prev, newState)); const updatePromise = (async () => { try { @@ -200,14 +205,13 @@ export default function OnboardingProvider({ ); const completeStep = useCallback( - (step: OnboardingStep) => { + (step: FrontendOnboardingStep) => { if (!state?.completedSteps?.includes(step)) { - updateState({ - completedSteps: [...(state?.completedSteps || []), step], - }); + postV1CompleteOnboardingStep({ step }); + fetchOnboarding(); } }, - [state?.completedSteps, updateState], + [state?.completedSteps, updateState, fetchOnboarding], ); const incrementRuns = useCallback(() => { @@ -229,18 +233,18 @@ export default function OnboardingProvider({ setNpsDialogOpen(true); } - updateState({ - agentRuns: newRunCount, - completedSteps: Array.from( - new Set([...state.completedSteps, ...milestoneSteps]), - ), - ...consecutiveData, - }); + // updateState({ + // agentRuns: newRunCount, + // completedSteps: Array.from( + // new Set([...state.completedSteps, ...milestoneSteps]), + // ), + // ...consecutiveData, + // }); }, [state, updateState]); return ( From bb9fed5746321a234733f5d1b504504733e1f20e Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 10 Nov 2025 20:13:02 +0900 Subject: [PATCH 16/33] Update provider --- .../Navbar/components/Wallet/Wallet.tsx | 55 ++++++----- .../src/providers/onboarding/helpers.ts | 53 +++++----- .../onboarding/onboarding-provider.tsx | 99 ++++++++++++++----- 3 files changed, 131 insertions(+), 76 deletions(-) diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx index 3146d961c82e..27ebad84f450 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx @@ -8,12 +8,10 @@ import { import { ScrollArea } from "@/components/__legacy__/ui/scroll-area"; import { Text } from "@/components/atoms/Text/Text"; import useCredits from "@/hooks/useCredits"; -import { - OnboardingStep, - WebSocketNotification, -} from "@/lib/autogpt-server-api"; +import { OnboardingStep, WebSocketNotification } from "@/lib/autogpt-server-api"; import { cn } from "@/lib/utils"; import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag"; import { storage, Key as StorageKey } from "@/services/storage/local-storage"; import { WalletIcon } from "@phosphor-icons/react"; @@ -23,7 +21,6 @@ import * as party from "party-js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { WalletRefill } from "./components/WalletRefill"; import { TaskGroups } from "./components/WalletTaskGroups"; -import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export interface Task { id: OnboardingStep; @@ -44,7 +41,7 @@ export interface TaskGroup { } export function Wallet() { - const { state, updateState, fetchOnboarding } = useOnboarding(); + const { state, updateState } = useOnboarding(); const isPaymentEnabled = useGetFlag(Flag.ENABLE_PLATFORM_PAYMENT); const groups = useMemo(() => { @@ -167,7 +164,6 @@ export function Wallet() { ]; }, [state]); - const api = useBackendAPI(); const { credits, formatCredits, fetchCredits } = useCredits({ fetchInitialCredits: true, }); @@ -250,35 +246,40 @@ export function Wallet() { [], ); - // Confetti effect on the wallet button + // React to onboarding notifications emitted by the provider + const api = useBackendAPI(); + const handleNotification = useCallback( (notification: WebSocketNotification) => { - if (notification.type !== "onboarding") { + if ( + notification.type !== "onboarding" || + notification.event !== "step_completed" + ) { return; } - if (walletRef.current) { - // Fix confetti appearing in the top left corner - const rect = walletRef.current.getBoundingClientRect(); - if (rect.width === 0 || rect.height === 0) { - return; - } - fetchCredits(); - fetchOnboarding(); - party.confetti(walletRef.current!, { - count: 30, - spread: 120, - shapes: ["square", "circle"], - size: party.variation.range(1, 2), - speed: party.variation.range(200, 300), - modules: [fadeOut], - }); + if (!walletRef.current) { + return; + } + + const rect = walletRef.current.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) { + return; } + + fetchCredits(); + party.confetti(walletRef.current, { + count: 30, + spread: 120, + shapes: ["square", "circle"], + size: party.variation.range(1, 2), + speed: party.variation.range(200, 300), + modules: [fadeOut], + }); }, - [], + [fetchCredits, fadeOut], ); - // WebSocket setup for onboarding notifications useEffect(() => { const detachMessage = api.onWebSocketMessage( "notification", diff --git a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts index e3bf3c6d74cc..875f3c901aeb 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts +++ b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts @@ -1,6 +1,14 @@ -import { GraphExecutionID, OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api"; +import { + GraphExecutionID, + OnboardingStep, + UserOnboarding, +} from "@/lib/autogpt-server-api"; import { UserOnboarding as RawUserOnboarding } from "@/app/api/__generated__/models/userOnboarding"; -import { UserOnboardingUpdate } from "@/app/api/__generated__/models/userOnboardingUpdate"; + +export type LocalOnboardingStateUpdate = Omit< + Partial, + "completedSteps" | "rewardedFor" | "lastRunAt" | "consecutiveRunDays" | "agentRuns" +>; export function isToday(date: Date): boolean { const today = new Date(); @@ -62,23 +70,17 @@ export function getRunMilestoneSteps( return steps; } -export function processOnboardingData( +export function fromBackendUserOnboarding( onboarding: RawUserOnboarding, ): UserOnboarding { return { - completedSteps: onboarding.completedSteps, - walletShown: onboarding.walletShown, - notified: onboarding.notified, - rewardedFor: onboarding.rewardedFor, + ...onboarding, usageReason: onboarding.usageReason || null, - integrations: onboarding.integrations, otherIntegrations: onboarding.otherIntegrations || null, selectedStoreListingVersionId: onboarding.selectedStoreListingVersionId || null, agentInput: onboarding.agentInput as {} || null, onboardingAgentExecutionId: onboarding.onboardingAgentExecutionId as GraphExecutionID || null, lastRunAt: onboarding.lastRunAt ? new Date(onboarding.lastRunAt) : null, - consecutiveRunDays: onboarding.consecutiveRunDays, - agentRuns: onboarding.agentRuns, }; } @@ -92,24 +94,23 @@ export function shouldRedirectFromOnboarding( ); } - export function updateOnboardingState( prevState: UserOnboarding | null, - newState: UserOnboardingUpdate, -): UserOnboarding { + newState: LocalOnboardingStateUpdate, +): UserOnboarding | null { return { - completedSteps: prevState?.completedSteps || [], - walletShown: newState.walletShown || prevState?.walletShown || true, - notified: newState.notified || prevState?.notified || [], - rewardedFor: prevState?.rewardedFor || [], - usageReason: newState.usageReason || prevState?.usageReason || null, - integrations: newState.integrations || prevState?.integrations || [], - otherIntegrations: newState.otherIntegrations || prevState?.otherIntegrations || null, - selectedStoreListingVersionId: newState.selectedStoreListingVersionId || prevState?.selectedStoreListingVersionId || null, - agentInput: newState.agentInput as Record || prevState?.agentInput || null, - onboardingAgentExecutionId: newState.onboardingAgentExecutionId as GraphExecutionID || prevState?.onboardingAgentExecutionId || null, - agentRuns: prevState?.agentRuns || 0, - lastRunAt: prevState?.lastRunAt || null, - consecutiveRunDays: prevState?.consecutiveRunDays || 0, + completedSteps: prevState?.completedSteps ?? [], + walletShown: newState.walletShown ?? prevState?.walletShown ?? false, + notified: newState.notified ?? prevState?.notified ?? [], + rewardedFor: prevState?.rewardedFor ?? [], + usageReason: newState.usageReason ?? prevState?.usageReason ?? null, + integrations: newState.integrations ?? prevState?.integrations ?? [], + otherIntegrations: newState.otherIntegrations ?? prevState?.otherIntegrations ?? null, + selectedStoreListingVersionId: newState.selectedStoreListingVersionId ?? prevState?.selectedStoreListingVersionId ?? null, + agentInput: newState.agentInput ?? prevState?.agentInput ?? null, + onboardingAgentExecutionId: newState.onboardingAgentExecutionId ?? prevState?.onboardingAgentExecutionId ?? null, + lastRunAt: prevState?.lastRunAt ?? null, + consecutiveRunDays: prevState?.consecutiveRunDays ?? 0, + agentRuns: prevState?.agentRuns ?? 0, }; } diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index 1d72a02bf3ca..c4bc1adf9f26 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -10,7 +10,11 @@ import { } from "@/components/__legacy__/ui/dialog"; import { useToast } from "@/components/molecules/Toast/use-toast"; import { useOnboardingTimezoneDetection } from "@/hooks/useOnboardingTimezoneDetection"; -import { OnboardingStep, UserOnboarding } from "@/lib/autogpt-server-api"; +import { + OnboardingStep, + UserOnboarding, + WebSocketNotification, +} from "@/lib/autogpt-server-api"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; import Link from "next/link"; @@ -28,8 +32,9 @@ import { calculateConsecutiveDays, updateOnboardingState, getRunMilestoneSteps, - processOnboardingData, + fromBackendUserOnboarding, shouldRedirectFromOnboarding, + LocalOnboardingStateUpdate, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; import { getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; @@ -41,14 +46,11 @@ type FrontendOnboardingStep = PostV1CompleteOnboardingStepStep; const OnboardingContext = createContext< | { state: UserOnboarding | null; - updateState: ( - state: Omit, "rewardedFor">, - ) => void; + updateState: (state: LocalOnboardingStateUpdate) => void; step: number; setStep: (step: number) => void; completeStep: (step: FrontendOnboardingStep) => void; incrementRuns: () => void; - fetchOnboarding: () => Promise; } | undefined >(undefined); @@ -63,15 +65,13 @@ export function useOnboarding(step?: number, completeStep?: FrontendOnboardingSt if ( !completeStep || !context.state || - !context.state.completedSteps || context.state.completedSteps.includes(completeStep) - ) + ) { return; + } - context.updateState({ - completedSteps: [...context.state.completedSteps, completeStep], - }); - }, [completeStep, context, context.updateState]); + context.completeStep(completeStep); + }, [completeStep, context]); useEffect(() => { if (step && context.step !== step) { @@ -122,8 +122,10 @@ export default function OnboardingProvider({ const fetchOnboarding = useCallback(async () => { const onboarding = await resolveResponse(getV1OnboardingState()); - const processedOnboarding = processOnboardingData(onboarding); - setState(processedOnboarding); + const processedOnboarding = fromBackendUserOnboarding(onboarding); + if (isMounted.current) { + setState(processedOnboarding); + } return processedOnboarding; }, []); @@ -173,9 +175,34 @@ export default function OnboardingProvider({ initializeOnboarding(); }, [api, isOnOnboardingRoute, router, user, isUserLoading, pathname, fetchOnboarding, toast]); + const handleOnboardingNotification = useCallback( + (notification: WebSocketNotification) => { + if (notification.type !== "onboarding") { + return; + } + + fetchOnboarding().catch((error) => { + console.error("Failed to refresh onboarding after notification:", error); + }); + }, + [fetchOnboarding], + ); + + useEffect(() => { + const detachMessage = api.onWebSocketMessage( + "notification", + handleOnboardingNotification, + ); + + api.connectWebSocket(); + + return () => { + detachMessage(); + }; + }, [api, handleOnboardingNotification]); + const updateState = useCallback( - (newState: UserOnboardingUpdate) => { - // Update local state immediately + (newState: LocalOnboardingStateUpdate) => { setState((prev) => updateOnboardingState(prev, newState)); const updatePromise = (async () => { @@ -194,24 +221,43 @@ export default function OnboardingProvider({ } })(); - // Track this pending update pendingUpdatesRef.current.add(updatePromise); updatePromise.finally(() => { pendingUpdatesRef.current.delete(updatePromise); }); }, - [api], + [toast], ); const completeStep = useCallback( (step: FrontendOnboardingStep) => { - if (!state?.completedSteps?.includes(step)) { - postV1CompleteOnboardingStep({ step }); - fetchOnboarding(); + if (state?.completedSteps?.includes(step)) { + return; } + + const completionPromise = (async () => { + try { + await postV1CompleteOnboardingStep({ step }); + await fetchOnboarding(); + } catch (error) { + if (isMounted.current) { + console.error("Failed to complete onboarding step:", error); + } + + toast({ + title: "Failed to complete onboarding step", + variant: "destructive", + }); + } + })(); + + pendingUpdatesRef.current.add(completionPromise); + completionPromise.finally(() => { + pendingUpdatesRef.current.delete(completionPromise); + }); }, - [state?.completedSteps, updateState, fetchOnboarding], + [state?.completedSteps, fetchOnboarding, toast], ); const incrementRuns = useCallback(() => { @@ -244,7 +290,14 @@ export default function OnboardingProvider({ return ( From c41e1cdb568c5080d81a8654384a84ffeb9c7fad Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 10 Nov 2025 20:29:24 +0900 Subject: [PATCH 17/33] Use `getV1RecommendedOnboardingAgents` --- .../(no-navbar)/onboarding/4-agent/page.tsx | 6 +++-- .../components/OnboardingAgentCard.tsx | 2 +- .../src/lib/autogpt-server-api/client.ts | 23 ------------------- .../src/lib/autogpt-server-api/types.ts | 22 ------------------ 4 files changed, 5 insertions(+), 48 deletions(-) diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/4-agent/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/4-agent/page.tsx index be874675b1c7..fe790d92849c 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/4-agent/page.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/4-agent/page.tsx @@ -9,10 +9,12 @@ import { OnboardingText } from "../components/OnboardingText"; import OnboardingAgentCard from "../components/OnboardingAgentCard"; import { useEffect, useState } from "react"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; -import { StoreAgentDetails } from "@/lib/autogpt-server-api"; import { isEmptyOrWhitespace } from "@/lib/utils"; import { useOnboarding } from "../../../../providers/onboarding/onboarding-provider"; import { finishOnboarding } from "../6-congrats/actions"; +import { getV1RecommendedOnboardingAgents } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { resolveResponse } from "@/app/api/helpers"; +import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails"; export default function Page() { const { state, updateState } = useOnboarding(4, "INTEGRATIONS"); @@ -20,7 +22,7 @@ export default function Page() { const api = useBackendAPI(); useEffect(() => { - api.getOnboardingAgents().then((agents) => { + resolveResponse(getV1RecommendedOnboardingAgents()).then((agents) => { if (agents.length < 2) { finishOnboarding(); } diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/components/OnboardingAgentCard.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/components/OnboardingAgentCard.tsx index 19d26d7cb3df..841b0bb50af0 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/components/OnboardingAgentCard.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/components/OnboardingAgentCard.tsx @@ -1,7 +1,7 @@ import { cn } from "@/lib/utils"; import StarRating from "./StarRating"; -import { StoreAgentDetails } from "@/lib/autogpt-server-api"; import SmartImage from "@/components/__legacy__/SmartImage"; +import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails"; type OnboardingAgentCardProps = { agent?: StoreAgentDetails; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index c08403127b0c..2eca76e8318d 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -56,7 +56,6 @@ import type { Schedule, ScheduleCreatable, ScheduleID, - StoreAgentDetails, StoreAgentsResponse, StoreListingsWithVersionsResponse, StoreReview, @@ -198,11 +197,6 @@ export default class BackendAPI { //////////////////////////////////////// ////////////// ONBOARDING ////////////// //////////////////////////////////////// - - getOnboardingAgents(): Promise { - return this._get("/onboarding/agents"); - } - /** Check if onboarding is enabled not if user finished it or not. */ isOnboardingEnabled(): Promise { return this._get("/onboarding/enabled"); @@ -460,29 +454,12 @@ export default class BackendAPI { return this._get("/store/agents", params); } - getStoreAgent( - username: string, - agentName: string, - ): Promise { - return this._get( - `/store/agents/${encodeURIComponent(username)}/${encodeURIComponent( - agentName, - )}`, - ); - } - getGraphMetaByStoreListingVersionID( storeListingVersionID: string, ): Promise { return this._get(`/store/graph/${storeListingVersionID}`); } - getStoreAgentByVersionId( - storeListingVersionID: string, - ): Promise { - return this._get(`/store/agents/${storeListingVersionID}`); - } - getStoreCreators(params?: { featured?: boolean; search_query?: string; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index ea82e34d3ed1..458df452551a 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -711,28 +711,6 @@ export type StoreAgentsResponse = { pagination: Pagination; }; -export type StoreAgentDetails = { - store_listing_version_id: string; - slug: string; - updated_at: string; - agent_name: string; - agent_video: string; - agent_image: string[]; - creator: string; - creator_avatar: string; - sub_heading: string; - description: string; - categories: string[]; - runs: number; - rating: number; - versions: string[]; - - // Approval and status fields - active_version_id?: string; - has_approved_version?: boolean; - is_available?: boolean; -}; - export type Creator = { name: string; username: string; From 96067cd62250785f49dc80133d72edb233d2dc7c Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 10 Nov 2025 20:31:53 +0900 Subject: [PATCH 18/33] Use `getV1IsOnboardingEnabled` --- autogpt_platform/frontend/src/app/api/helpers.ts | 4 ++-- .../frontend/src/lib/autogpt-server-api/client.ts | 8 -------- .../src/providers/onboarding/onboarding-provider.tsx | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/autogpt_platform/frontend/src/app/api/helpers.ts b/autogpt_platform/frontend/src/app/api/helpers.ts index 3e6009a298cb..d85b01a3f063 100644 --- a/autogpt_platform/frontend/src/app/api/helpers.ts +++ b/autogpt_platform/frontend/src/app/api/helpers.ts @@ -1,5 +1,5 @@ import BackendAPI from "@/lib/autogpt-server-api"; -import { getV1OnboardingState } from "./__generated__/endpoints/onboarding/onboarding"; +import { getV1IsOnboardingEnabled, getV1OnboardingState } from "./__generated__/endpoints/onboarding/onboarding"; /** * Narrow an orval response to its success payload if and only if it is a `200` status with OK shape. @@ -90,7 +90,7 @@ export async function resolveResponse< export async function shouldShowOnboarding() { const api = new BackendAPI(); - const isEnabled = await api.isOnboardingEnabled(); + const isEnabled = await resolveResponse(getV1IsOnboardingEnabled()); const onboarding = await resolveResponse(getV1OnboardingState()); const isCompleted = onboarding.completedSteps.includes("CONGRATS"); return isEnabled && !isCompleted; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 2eca76e8318d..0b95d1c49a10 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -194,14 +194,6 @@ export default class BackendAPI { return this._request("PATCH", "/credits"); } - //////////////////////////////////////// - ////////////// ONBOARDING ////////////// - //////////////////////////////////////// - /** Check if onboarding is enabled not if user finished it or not. */ - isOnboardingEnabled(): Promise { - return this._get("/onboarding/enabled"); - } - //////////////////////////////////////// //////////////// GRAPHS //////////////// //////////////////////////////////////// diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index c4bc1adf9f26..40106366a2c7 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -37,7 +37,7 @@ import { LocalOnboardingStateUpdate, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; -import { getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { getV1IsOnboardingEnabled, getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; import { UserOnboardingUpdate } from "@/app/api/__generated__/models/userOnboardingUpdate"; import { PostV1CompleteOnboardingStepStep } from "@/app/api/__generated__/models/postV1CompleteOnboardingStepStep"; @@ -141,7 +141,7 @@ export default function OnboardingProvider({ try { // Check onboarding enabled only for onboarding routes if (isOnOnboardingRoute) { - const enabled = await api.isOnboardingEnabled(); + const enabled = await resolveResponse(getV1IsOnboardingEnabled()); if (!enabled) { router.push("/marketplace"); return; From 688c784fc31388c1ab1d84201ca193082e73d89b Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Tue, 11 Nov 2025 11:09:53 +0900 Subject: [PATCH 19/33] Backend increment runs logic --- .../backend/backend/data/onboarding.py | 100 ++++++++++++++++-- .../backend/server/integrations/router.py | 3 +- .../backend/backend/server/routers/v1.py | 2 + .../server/v2/library/routes/presets.py | 4 + .../onboarding/5-run/useOnboardingRunStep.tsx | 12 +-- .../OldAgentLibraryView.tsx | 15 +-- .../components/cron-scheduler-dialog.tsx | 1 - .../frontend/src/hooks/useAgentGraph.tsx | 5 +- .../src/lib/autogpt-server-api/types.ts | 2 +- .../src/providers/onboarding/helpers.ts | 60 ----------- .../onboarding/onboarding-provider.tsx | 37 +------ 11 files changed, 111 insertions(+), 130 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 70024e1b856e..676fc1e52a95 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -1,5 +1,7 @@ import re +from datetime import datetime, timedelta, timezone from typing import Any, Literal, Optional +from zoneinfo import ZoneInfo import prisma import pydantic @@ -14,10 +16,12 @@ AsyncRedisNotificationEventBus, NotificationEvent, ) -from backend.server.model import OnboardingNotificationPayload +from backend.data.user import get_user_by_id +from backend.server.model import NotificationPayload, OnboardingNotificationPayload from backend.server.v2.store.model import StoreAgentDetails from backend.util.cache import cached from backend.util.json import SafeJson +from backend.util.timezone_utils import get_user_timezone_or_utc # Mapping from user reason id to categories to search for when choosing agent to show REASON_MAPPING: dict[str, list[str]] = { @@ -44,10 +48,6 @@ OnboardingStep.MARKETPLACE_RUN_AGENT, OnboardingStep.BUILDER_SAVE_AGENT, OnboardingStep.RE_RUN_AGENT, - OnboardingStep.RUN_AGENTS, - OnboardingStep.RUN_3_DAYS, - OnboardingStep.RUN_14_DAYS, - OnboardingStep.RUN_AGENTS_100, OnboardingStep.BUILDER_OPEN, OnboardingStep.BUILDER_RUN_AGENT, ] @@ -191,13 +191,15 @@ async def complete_onboarding_step(user_id: str, step: OnboardingStep): await _send_onboarding_notification(user_id, step) -async def _send_onboarding_notification(user_id: str, step: OnboardingStep): +async def _send_onboarding_notification( + user_id: str, step: OnboardingStep, event: str = "step_completed" +): """ - Sends an onboarding notification to the user for the specified step. + Sends an onboarding notification to the user. """ payload = OnboardingNotificationPayload( type="onboarding", - event="step_completed", + event=event, step=step.value, ) await AsyncRedisNotificationEventBus().publish( @@ -228,7 +230,7 @@ def clean_and_split(text: str) -> list[str]: return words -def calculate_points( +def _calculate_points( agent, categories: list[str], custom: list[str], integrations: list[str] ) -> int: """ @@ -286,6 +288,84 @@ def get_credentials_blocks() -> dict[str, str]: CREDENTIALS_FIELDS: dict[str, str] = get_credentials_blocks() +def _normalize_datetime(value: datetime | None) -> datetime | None: + if value is None: + return None + if value.tzinfo is None: + return value.replace(tzinfo=timezone.utc) + return value.astimezone(timezone.utc) + + +def _calculate_consecutive_run_days( + last_run_at: datetime | None, current_consecutive_days: int, user_timezone: str +) -> tuple[datetime, int]: + tz = ZoneInfo(user_timezone) + local_now = datetime.now(tz) + normalized_last_run = _normalize_datetime(last_run_at) + + if normalized_last_run is None: + return local_now.astimezone(timezone.utc), 1 + + last_run_local = normalized_last_run.astimezone(tz) + last_run_date = last_run_local.date() + today = local_now.date() + + if last_run_date == today: + return local_now.astimezone(timezone.utc), current_consecutive_days + + if last_run_date == today - timedelta(days=1): + return local_now.astimezone(timezone.utc), current_consecutive_days + 1 + + return local_now.astimezone(timezone.utc), 1 + + +def _get_run_milestone_steps( + new_run_count: int, consecutive_days: int +) -> list[OnboardingStep]: + milestones: list[OnboardingStep] = [] + if new_run_count >= 10: + milestones.append(OnboardingStep.RUN_AGENTS) + if new_run_count >= 100: + milestones.append(OnboardingStep.RUN_AGENTS_100) + if consecutive_days >= 3: + milestones.append(OnboardingStep.RUN_3_DAYS) + if consecutive_days >= 14: + milestones.append(OnboardingStep.RUN_14_DAYS) + return milestones + + +async def _get_user_timezone(user_id: str) -> str: + user = await get_user_by_id(user_id) + return get_user_timezone_or_utc(user.timezone if user else None) + + +async def increment_runs(user_id: str): + """ + Increment a user's run counters and trigger any onboarding milestones. + """ + user_timezone = await _get_user_timezone(user_id) + onboarding = await get_user_onboarding(user_id) + new_run_count = onboarding.agentRuns + 1 + last_run_at, consecutive_run_days = _calculate_consecutive_run_days( + onboarding.lastRunAt, onboarding.consecutiveRunDays, user_timezone + ) + + await UserOnboarding.prisma().update( + where={"userId": user_id}, + data={ + "agentRuns": new_run_count, + "lastRunAt": last_run_at, + "consecutiveRunDays": consecutive_run_days, + }, + ) + + milestones = _get_run_milestone_steps(new_run_count, consecutive_run_days) + new_steps = [step for step in milestones if step not in onboarding.completedSteps] + + for step in new_steps: + await complete_onboarding_step(user_id, step) + + async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: user_onboarding = await get_user_onboarding(user_id) categories = REASON_MAPPING.get(user_onboarding.usageReason or "", []) @@ -340,7 +420,7 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: # Calculate points for the first X agents and choose the top 2 agent_points = [] for agent in storeAgents[:POINTS_AGENT_COUNT]: - points = calculate_points( + points = _calculate_points( agent, categories, custom, user_onboarding.integrations ) agent_points.append((agent, points)) diff --git a/autogpt_platform/backend/backend/server/integrations/router.py b/autogpt_platform/backend/backend/server/integrations/router.py index 36523e867a92..3ab5fd1b2194 100644 --- a/autogpt_platform/backend/backend/server/integrations/router.py +++ b/autogpt_platform/backend/backend/server/integrations/router.py @@ -33,7 +33,7 @@ OAuth2Credentials, UserIntegrations, ) -from backend.data.onboarding import OnboardingStep, complete_onboarding_step +from backend.data.onboarding import OnboardingStep, complete_onboarding_step, increment_runs from backend.data.user import get_user_integrations from backend.executor.utils import add_graph_execution from backend.integrations.ayrshare import AyrshareClient, SocialPlatform @@ -377,6 +377,7 @@ async def webhook_ingress_generic( return await complete_onboarding_step(user_id, OnboardingStep.TRIGGER_WEBHOOK) + await increment_runs(user_id) # Execute all triggers concurrently for better performance tasks = [] diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 40017805f9aa..f8d0bc8d5e8e 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -54,6 +54,7 @@ UserOnboardingUpdate, OnboardingStep, complete_onboarding_step, + increment_runs, get_recommended_agents, get_user_onboarding, onboarding_enabled, @@ -925,6 +926,7 @@ async def execute_graph( # Record successful graph execution record_graph_execution(graph_id=graph_id, status="success", user_id=user_id) record_graph_operation(operation="execute", status="success") + await increment_runs(user_id) return result except GraphValidationError as e: # Record failed graph execution diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py index 12bc77629a7a..c68474ac5b7c 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py @@ -1,6 +1,8 @@ import logging from typing import Any, Optional +from backend.data.onboarding import increment_runs + import autogpt_libs.auth as autogpt_auth_lib from fastapi import APIRouter, Body, HTTPException, Query, Security, status @@ -401,6 +403,8 @@ async def execute_preset( merged_node_input = preset.inputs | inputs merged_credential_inputs = preset.credentials | credential_inputs + await increment_runs(user_id) + return await add_graph_execution( user_id=user_id, graph_id=preset.graph_id, diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx index 26c5fc4c24f0..ea9bff24af30 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx @@ -80,12 +80,7 @@ export function useOnboardingRunStep() { setShowInput(true); onboarding.setStep(6); - onboarding.updateState({ - completedSteps: [ - ...(onboarding.state.completedSteps || []), - "AGENT_NEW_RUN", - ], - }); + onboarding.completeStep("AGENT_NEW_RUN"); } function handleSetAgentInput(key: string, value: string) { @@ -125,10 +120,7 @@ export function useOnboardingRunStep() { inputCredentials, ); - onboarding.updateState({ - onboardingAgentExecutionId: runID, - agentRuns: (onboarding.state.agentRuns || 0) + 1, - }); + onboarding.updateState({ onboardingAgentExecutionId: runID }); router.push("/onboarding/6-congrats"); } catch (error) { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx index 28a3585df8ca..046d1f873bef 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx @@ -86,8 +86,7 @@ export function OldAgentLibraryView() { useState(null); const { state: onboardingState, - updateState: updateOnboardingState, - incrementRuns, + completeStep: completeOnboardingState, } = useOnboarding(); const [copyAgentDialogOpen, setCopyAgentDialogOpen] = useState(false); const [creatingPresetFromExecutionID, setCreatingPresetFromExecutionID] = @@ -146,11 +145,9 @@ export function OldAgentLibraryView() { return; if (selectedRun.id === onboardingState.onboardingAgentExecutionId) { - updateOnboardingState({ - completedSteps: [...onboardingState.completedSteps, "GET_RESULTS"], - }); + completeOnboardingState("GET_RESULTS"); } - }, [selectedRun, onboardingState, updateOnboardingState]); + }, [selectedRun, onboardingState, completeOnboardingState]); const lastRefresh = useRef(0); const refreshPageData = useCallback(() => { @@ -285,10 +282,6 @@ export function OldAgentLibraryView() { (data) => { if (data.graph_id != agent?.graph_id) return; - if (data.status == "COMPLETED") { - incrementRuns(); - } - agentRunsQuery.upsertAgentRun(data); if (data.id === selectedView.id) { // Update currently viewed run @@ -300,7 +293,7 @@ export function OldAgentLibraryView() { return () => { detachExecUpdateHandler(); }; - }, [api, agent?.graph_id, selectedView.id, incrementRuns]); + }, [api, agent?.graph_id, selectedView.id]); // Pre-load selectedRun based on selectedView useEffect(() => { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx index 6b5124113188..ca8a5fe624b6 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx @@ -49,7 +49,6 @@ export function CronSchedulerDialog(props: CronSchedulerDialogProps) { const [scheduleName, setScheduleName] = useState( props.mode === "with-name" ? props.defaultScheduleName || "" : "", ); - const { completeStep } = useOnboarding(); // Get user's timezone const { data: userTimezone } = useGetV1GetUserTimezone({ diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx index fd2768642a97..ad1725909afb 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx @@ -65,7 +65,7 @@ export default function useAgentGraph( ); const [xyNodes, setXYNodes] = useState([]); const [xyEdges, setXYEdges] = useState([]); - const { state, completeStep, incrementRuns } = useOnboarding(); + const { state, completeStep } = useOnboarding(); const betaBlocks = useGetFlag(Flag.BETA_BLOCKS); // Filter blocks based on beta flags @@ -577,14 +577,13 @@ export default function useAgentGraph( setIsRunning(false); setIsStopping(false); setActiveExecutionID(null); - incrementRuns(); } }, ); }; fetchExecutions(); - }, [flowID, flowExecutionID, incrementRuns]); + }, [flowID, flowExecutionID]); const prepareNodeInputData = useCallback( (node: CustomNode) => { diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index 458df452551a..326625ee121c 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -956,7 +956,7 @@ export interface UserOnboarding { export interface OnboardingNotificationPayload { type: "onboarding"; - event: string; + event: "step_completed"; step: OnboardingStep; } diff --git a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts index 875f3c901aeb..3b8a52793a5a 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts +++ b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts @@ -10,66 +10,6 @@ export type LocalOnboardingStateUpdate = Omit< "completedSteps" | "rewardedFor" | "lastRunAt" | "consecutiveRunDays" | "agentRuns" >; -export function isToday(date: Date): boolean { - const today = new Date(); - return ( - date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear() - ); -} - -export function isYesterday(date: Date): boolean { - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - - return ( - date.getDate() === yesterday.getDate() && - date.getMonth() === yesterday.getMonth() && - date.getFullYear() === yesterday.getFullYear() - ); -} - -export function calculateConsecutiveDays( - lastRunAt: Date | null, - currentConsecutiveDays: number, -): { lastRunAt: Date; consecutiveRunDays: number } { - const now = new Date(); - - if (lastRunAt === null || isYesterday(lastRunAt)) { - return { - lastRunAt: now, - consecutiveRunDays: currentConsecutiveDays + 1, - }; - } - - if (!isToday(lastRunAt)) { - return { - lastRunAt: now, - consecutiveRunDays: 1, - }; - } - - return { - lastRunAt: now, - consecutiveRunDays: currentConsecutiveDays, - }; -} - -export function getRunMilestoneSteps( - newRunCount: number, - consecutiveDays: number, -): OnboardingStep[] { - const steps: OnboardingStep[] = []; - - if (newRunCount >= 10) steps.push("RUN_AGENTS"); - if (newRunCount >= 100) steps.push("RUN_AGENTS_100"); - if (consecutiveDays >= 3) steps.push("RUN_3_DAYS"); - if (consecutiveDays >= 14) steps.push("RUN_14_DAYS"); - - return steps; -} - export function fromBackendUserOnboarding( onboarding: RawUserOnboarding, ): UserOnboarding { diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index 40106366a2c7..58d4106d03b9 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -29,16 +29,13 @@ import { useState, } from "react"; import { - calculateConsecutiveDays, updateOnboardingState, - getRunMilestoneSteps, fromBackendUserOnboarding, shouldRedirectFromOnboarding, LocalOnboardingStateUpdate, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; import { getV1IsOnboardingEnabled, getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; -import { UserOnboardingUpdate } from "@/app/api/__generated__/models/userOnboardingUpdate"; import { PostV1CompleteOnboardingStepStep } from "@/app/api/__generated__/models/postV1CompleteOnboardingStepStep"; type FrontendOnboardingStep = PostV1CompleteOnboardingStepStep; @@ -50,7 +47,6 @@ const OnboardingContext = createContext< step: number; setStep: (step: number) => void; completeStep: (step: FrontendOnboardingStep) => void; - incrementRuns: () => void; } | undefined >(undefined); @@ -181,6 +177,10 @@ export default function OnboardingProvider({ return; } + if (notification.step === "RUN_AGENTS") { + setNpsDialogOpen(true); + } + fetchOnboarding().catch((error) => { console.error("Failed to refresh onboarding after notification:", error); }); @@ -260,34 +260,6 @@ export default function OnboardingProvider({ [state?.completedSteps, fetchOnboarding, toast], ); - const incrementRuns = useCallback(() => { - if (!state?.completedSteps) return; - - const newRunCount = state.agentRuns + 1; - const consecutiveData = calculateConsecutiveDays( - state.lastRunAt, - state.consecutiveRunDays, - ); - - const milestoneSteps = getRunMilestoneSteps( - newRunCount, - consecutiveData.consecutiveRunDays, - ); - - // Show NPS dialog at 10 runs - if (newRunCount === 10) { - setNpsDialogOpen(true); - } - - // updateState({ - // agentRuns: newRunCount, - // completedSteps: Array.from( - // new Set([...state.completedSteps, ...milestoneSteps]), - // ), - // ...consecutiveData, - // }); - }, [state, updateState]); - return ( From 6e58fa70ab3b1a8f95b31f95a9fa9ba395df9391 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Tue, 11 Nov 2025 18:33:05 +0900 Subject: [PATCH 20/33] Backend `GET_RESULTS` --- .../backend/backend/data/onboarding.py | 10 +++++++++- .../backend/backend/server/routers/v1.py | 11 +++++++++++ .../OldAgentLibraryView/OldAgentLibraryView.tsx | 16 +--------------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 676fc1e52a95..148612166ab8 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -42,7 +42,6 @@ OnboardingStep.AGENT_NEW_RUN, OnboardingStep.AGENT_INPUT, OnboardingStep.CONGRATS, - OnboardingStep.GET_RESULTS, OnboardingStep.MARKETPLACE_VISIT, OnboardingStep.MARKETPLACE_ADD_AGENT, OnboardingStep.MARKETPLACE_RUN_AGENT, @@ -366,6 +365,15 @@ async def increment_runs(user_id: str): await complete_onboarding_step(user_id, step) +async def complete_get_results_if_applicable(user_id: str, graph_exec_id: str) -> None: + onboarding = await get_user_onboarding(user_id) + if ( + onboarding.onboardingAgentExecutionId == graph_exec_id + and OnboardingStep.GET_RESULTS not in onboarding.completedSteps + ): + await complete_onboarding_step(user_id, OnboardingStep.GET_RESULTS) + + async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: user_onboarding = await get_user_onboarding(user_id) categories = REASON_MAPPING.get(user_onboarding.usageReason or "", []) diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index f8d0bc8d5e8e..a8aab92c5465 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -54,6 +54,7 @@ UserOnboardingUpdate, OnboardingStep, complete_onboarding_step, + complete_get_results_if_applicable, increment_runs, get_recommended_agents, get_user_onboarding, @@ -1059,6 +1060,16 @@ async def get_graph_execution( status_code=404, detail=f"Graph execution #{graph_exec_id} not found." ) + try: + await complete_get_results_if_applicable(user_id, graph_exec_id) + except Exception: + logger.warning( + "Failed to auto-complete GET_RESULTS for user %s exec %s", + user_id, + graph_exec_id, + exc_info=True, + ) + return result diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx index 046d1f873bef..27162fd4cf72 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx @@ -86,7 +86,7 @@ export function OldAgentLibraryView() { useState(null); const { state: onboardingState, - completeStep: completeOnboardingState, + completeStep: completeOnboardingStep, } = useOnboarding(); const [copyAgentDialogOpen, setCopyAgentDialogOpen] = useState(false); const [creatingPresetFromExecutionID, setCreatingPresetFromExecutionID] = @@ -135,20 +135,6 @@ export function OldAgentLibraryView() { [api, graphVersions, loadingGraphVersions], ); - // Reward user for viewing results of their onboarding agent - useEffect(() => { - if ( - !onboardingState || - !selectedRun || - onboardingState.completedSteps.includes("GET_RESULTS") - ) - return; - - if (selectedRun.id === onboardingState.onboardingAgentExecutionId) { - completeOnboardingState("GET_RESULTS"); - } - }, [selectedRun, onboardingState, completeOnboardingState]); - const lastRefresh = useRef(0); const refreshPageData = useCallback(() => { if (Date.now() - lastRefresh.current < 2e3) return; // 2 second debounce From c185f28a95767fce8a3ae2a8d174e5f69d27b500 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Tue, 11 Nov 2025 18:44:23 +0900 Subject: [PATCH 21/33] Backend `MARKETPLACE_ADD_AGENT` --- autogpt_platform/backend/backend/data/onboarding.py | 2 +- .../backend/backend/server/v2/library/routes/agents.py | 7 ++++++- .../components/OldAgentLibraryView/OldAgentLibraryView.tsx | 5 ----- .../marketplace/components/AgentInfo/useAgentInfo.ts | 4 ---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 148612166ab8..323fece5453a 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -43,7 +43,7 @@ OnboardingStep.AGENT_INPUT, OnboardingStep.CONGRATS, OnboardingStep.MARKETPLACE_VISIT, - OnboardingStep.MARKETPLACE_ADD_AGENT, + OnboardingStep.MARKETPLACE_RUN_AGENT, # <- OnboardingStep.MARKETPLACE_RUN_AGENT, OnboardingStep.BUILDER_SAVE_AGENT, OnboardingStep.RE_RUN_AGENT, diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py index 1bdf255ce50e..9f1de9ec8ed2 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py @@ -1,10 +1,13 @@ import logging from typing import Optional +from prisma.enums import OnboardingStep + import autogpt_libs.auth as autogpt_auth_lib from fastapi import APIRouter, Body, HTTPException, Query, Security, status from fastapi.responses import Response +from backend.data.onboarding import complete_onboarding_step import backend.server.v2.library.db as library_db import backend.server.v2.library.model as library_model import backend.server.v2.store.exceptions as store_exceptions @@ -210,10 +213,12 @@ async def add_marketplace_agent_to_library( HTTPException(500): If a server/database error occurs. """ try: - return await library_db.add_store_agent_to_library( + agent = await library_db.add_store_agent_to_library( store_listing_version_id=store_listing_version_id, user_id=user_id, ) + await complete_onboarding_step(user_id, OnboardingStep.MARKETPLACE_ADD_AGENT) + return agent except store_exceptions.AgentNotFoundError as e: logger.warning( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx index 27162fd4cf72..a7fdb0a9ff85 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx @@ -47,7 +47,6 @@ import { CreatePresetDialog } from "./components/create-preset-dialog"; import { useAgentRunsInfinite } from "./use-agent-runs"; import { AgentRunsSelectorList } from "./components/agent-runs-selector-list"; import { AgentScheduleDetailsView } from "./components/agent-schedule-details-view"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; export function OldAgentLibraryView() { const { id: agentID }: { id: LibraryAgentID } = useParams(); @@ -84,10 +83,6 @@ export function OldAgentLibraryView() { useState(null); const [confirmingDeleteAgentPreset, setConfirmingDeleteAgentPreset] = useState(null); - const { - state: onboardingState, - completeStep: completeOnboardingStep, - } = useOnboarding(); const [copyAgentDialogOpen, setCopyAgentDialogOpen] = useState(false); const [creatingPresetFromExecutionID, setCreatingPresetFromExecutionID] = useState(null); diff --git a/autogpt_platform/frontend/src/app/(platform)/marketplace/components/AgentInfo/useAgentInfo.ts b/autogpt_platform/frontend/src/app/(platform)/marketplace/components/AgentInfo/useAgentInfo.ts index b9558c09f448..37d8a5d75acd 100644 --- a/autogpt_platform/frontend/src/app/(platform)/marketplace/components/AgentInfo/useAgentInfo.ts +++ b/autogpt_platform/frontend/src/app/(platform)/marketplace/components/AgentInfo/useAgentInfo.ts @@ -6,7 +6,6 @@ import { useToast } from "@/components/molecules/Toast/use-toast"; import { useRouter } from "next/navigation"; import * as Sentry from "@sentry/nextjs"; import { useGetV2DownloadAgentFile } from "@/app/api/__generated__/endpoints/store/store"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; import { analytics } from "@/services/analytics"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { useQueryClient } from "@tanstack/react-query"; @@ -18,7 +17,6 @@ interface UseAgentInfoProps { export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => { const { toast } = useToast(); const router = useRouter(); - const { completeStep } = useOnboarding(); const queryClient = useQueryClient(); const { @@ -49,8 +47,6 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => { const data = response as LibraryAgent; if (isAddingAgentFirstTime) { - completeStep("MARKETPLACE_ADD_AGENT"); - await queryClient.invalidateQueries({ queryKey: getGetV2ListLibraryAgentsQueryKey(), }); From 0f51a8e93e7c0c3e4d5ffd1f48728f54b81be1c5 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 12 Nov 2025 19:14:22 +0900 Subject: [PATCH 22/33] Backend `BUILDER_SAVE_AGENT` --- autogpt_platform/backend/backend/data/onboarding.py | 1 - autogpt_platform/backend/backend/server/model.py | 3 ++- autogpt_platform/backend/backend/server/routers/v1.py | 7 ++++++- .../NewSaveControl/useNewSaveControl.ts | 2 +- .../useLibraryUploadAgentDialog.ts | 1 + .../monitoring/components/AgentImportForm.tsx | 2 +- autogpt_platform/frontend/src/hooks/useAgentGraph.tsx | 8 ++------ .../frontend/src/lib/autogpt-server-api/client.ts | 11 +++++++++-- 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 323fece5453a..ea8342e4e4fe 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -45,7 +45,6 @@ OnboardingStep.MARKETPLACE_VISIT, OnboardingStep.MARKETPLACE_RUN_AGENT, # <- OnboardingStep.MARKETPLACE_RUN_AGENT, - OnboardingStep.BUILDER_SAVE_AGENT, OnboardingStep.RE_RUN_AGENT, OnboardingStep.BUILDER_OPEN, OnboardingStep.BUILDER_RUN_AGENT, diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index 24ba4fa7eeff..64a97f628209 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -1,5 +1,5 @@ import enum -from typing import Any, Optional +from typing import Any, Literal, Optional import pydantic @@ -37,6 +37,7 @@ class WSSubscribeGraphExecutionsRequest(pydantic.BaseModel): class CreateGraph(pydantic.BaseModel): graph: Graph + source: Literal["builder", "upload"] | None = None class CreateAPIKeyRequest(pydantic.BaseModel): diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index a8aab92c5465..2726a7d483ff 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -778,7 +778,12 @@ async def create_new_graph( # as the graph already valid and no sub-graphs are returned back. await graph_db.create_graph(graph, user_id=user_id) await library_db.create_library_agent(graph, user_id=user_id) - return await on_graph_activate(graph, user_id=user_id) + activated_graph = await on_graph_activate(graph, user_id=user_id) + + if create_graph.source == "builder": + await complete_onboarding_step(user_id, OnboardingStep.BUILDER_SAVE_AGENT) + + return activated_graph @v1_router.delete( diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSaveControl/useNewSaveControl.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSaveControl/useNewSaveControl.ts index fbfcedd03f2e..1598c2a321a0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSaveControl/useNewSaveControl.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSaveControl/useNewSaveControl.ts @@ -152,7 +152,7 @@ export const useNewSaveControl = ({ nodes: graphNodes, links: graphLinks, }; - await createNewGraph({ data: { graph: data } }); + await createNewGraph({ data: { graph: data, source: "builder" } }); } }; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryUploadAgentDialog/useLibraryUploadAgentDialog.ts b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryUploadAgentDialog/useLibraryUploadAgentDialog.ts index 4bbd0eb2e6cf..689753a3402a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryUploadAgentDialog/useLibraryUploadAgentDialog.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryUploadAgentDialog/useLibraryUploadAgentDialog.ts @@ -62,6 +62,7 @@ export const useLibraryUploadAgentDialog = () => { await createGraph({ data: { graph: payload, + source: "upload", }, }); }; diff --git a/autogpt_platform/frontend/src/app/(platform)/monitoring/components/AgentImportForm.tsx b/autogpt_platform/frontend/src/app/(platform)/monitoring/components/AgentImportForm.tsx index 991ea4868a2d..7692640d2f69 100644 --- a/autogpt_platform/frontend/src/app/(platform)/monitoring/components/AgentImportForm.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/monitoring/components/AgentImportForm.tsx @@ -62,7 +62,7 @@ export const AgentImportForm: React.FC< }; api - .createGraph(payload) + .createGraph(payload, "upload") .then((response) => { const qID = "flowID"; window.location.href = `/build?${qID}=${response.id}`; diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx index ad1725909afb..2c1731a16f43 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx @@ -700,7 +700,7 @@ export default function useAgentGraph( ...payload, id: savedAgent.id, }) - : await api.createGraph(payload); + : await api.createGraph(payload, "builder"); console.debug("Response from the API:", newSavedAgent); } @@ -772,8 +772,6 @@ export default function useAgentGraph( await queryClient.invalidateQueries({ queryKey: getGetV2ListLibraryAgentsQueryKey(), }); - - completeStep("BUILDER_SAVE_AGENT"); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -786,7 +784,7 @@ export default function useAgentGraph( } finally { setIsSaving(false); } - }, [_saveAgent, toast, completeStep]); + }, [_saveAgent, toast]); const saveAndRun = useCallback( async ( @@ -801,7 +799,6 @@ export default function useAgentGraph( let savedAgent: Graph; try { savedAgent = await _saveAgent(); - completeStep("BUILDER_SAVE_AGENT"); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -892,7 +889,6 @@ export default function useAgentGraph( [ _saveAgent, toast, - completeStep, api, searchParams, pathname, diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 0b95d1c49a10..f014b37442a9 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -227,8 +227,14 @@ export default class BackendAPI { return this._get(`/graphs/${id}/versions`); } - createGraph(graph: GraphCreatable): Promise { - const requestBody = { graph } as GraphCreateRequestBody; + createGraph( + graph: GraphCreatable, + source?: "builder" | "upload", + ): Promise { + const requestBody: GraphCreateRequestBody = { graph }; + if (source) { + requestBody.source = source; + } return this._request("POST", "/graphs", requestBody); } @@ -1291,6 +1297,7 @@ declare global { type GraphCreateRequestBody = { graph: GraphCreatable; + source?: "builder" | "upload"; }; type WebsocketMessageTypeMap = { From f938e4de89f5410f4ef4bc7c4c8b9ecec3485602 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 12 Nov 2025 20:01:30 +0900 Subject: [PATCH 23/33] Track source of execution --- .../backend/backend/data/onboarding.py | 4 +--- .../backend/backend/server/model.py | 6 ++++- .../backend/backend/server/routers/v1.py | 8 +++++++ .../onboarding/5-run/useOnboardingRunStep.tsx | 1 + .../components/RunGraph/useRunGraph.ts | 2 +- .../RunInputDialog/useRunInputDialog.ts | 6 ++++- .../RunAgentModal/useAgentRunModal.ts | 1 + .../RunDetailHeader/useRunDetailHeader.ts | 1 + .../components/agent-run-details-view.tsx | 1 + .../components/agent-run-draft-view.tsx | 13 ++++++----- .../agent-schedule-details-view.tsx | 1 + .../frontend/src/app/api/openapi.json | 18 ++++++++++++++- .../frontend/src/hooks/useAgentGraph.tsx | 8 +------ .../src/lib/autogpt-server-api/client.ts | 23 ++++++++++++++----- 14 files changed, 67 insertions(+), 26 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index ea8342e4e4fe..f59f69af7609 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -43,11 +43,9 @@ OnboardingStep.AGENT_INPUT, OnboardingStep.CONGRATS, OnboardingStep.MARKETPLACE_VISIT, - OnboardingStep.MARKETPLACE_RUN_AGENT, # <- - OnboardingStep.MARKETPLACE_RUN_AGENT, + OnboardingStep.RE_RUN_AGENT, # <- OnboardingStep.RE_RUN_AGENT, OnboardingStep.BUILDER_OPEN, - OnboardingStep.BUILDER_RUN_AGENT, ] diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index 64a97f628209..1f627362c367 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -35,9 +35,13 @@ class WSSubscribeGraphExecutionsRequest(pydantic.BaseModel): graph_id: str +GraphCreationSource = Literal["builder", "upload"] +GraphExecutionSource = Literal["builder", "library", "onboarding"] + + class CreateGraph(pydantic.BaseModel): graph: Graph - source: Literal["builder", "upload"] | None = None + source: GraphCreationSource | None = None class CreateAPIKeyRequest(pydantic.BaseModel): diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 2726a7d483ff..25e054c790a3 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -85,6 +85,7 @@ CreateAPIKeyRequest, CreateAPIKeyResponse, CreateGraph, + GraphExecutionSource, RequestTopUp, SetGraphActiveVersion, TimezoneResponse, @@ -909,6 +910,9 @@ async def execute_graph( credentials_inputs: Annotated[ dict[str, CredentialsMetaInput], Body(..., embed=True, default_factory=dict) ], + source: Annotated[ + GraphExecutionSource | None, Body(default=None, embed=True) + ] = None, graph_version: Optional[int] = None, preset_id: Optional[str] = None, ) -> execution_db.GraphExecutionMeta: @@ -933,6 +937,10 @@ async def execute_graph( record_graph_execution(graph_id=graph_id, status="success", user_id=user_id) record_graph_operation(operation="execute", status="success") await increment_runs(user_id) + if source == "library": + await complete_onboarding_step(user_id, OnboardingStep.MARKETPLACE_RUN_AGENT) + elif source == "builder": + await complete_onboarding_step(user_id, OnboardingStep.BUILDER_RUN_AGENT) return result except GraphValidationError as e: # Record failed graph execution diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx index ea9bff24af30..812d50b95db5 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx @@ -118,6 +118,7 @@ export function useOnboardingRunStep() { libraryAgent.graph_version, onboarding.state.agentInput || {}, inputCredentials, + "onboarding", ); onboarding.updateState({ onboardingAgentExecutionId: runID }); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts index bbdb44123bdc..99db0b64398e 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts @@ -76,7 +76,7 @@ export const useRunGraph = () => { await executeGraph({ graphId: flowID ?? "", graphVersion: flowVersion || null, - data: { inputs: {}, credentials_inputs: {} }, + data: { inputs: {}, credentials_inputs: {}, source: "builder" }, }); } }; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts index c0621eeaae06..2723edfbc474 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts @@ -85,7 +85,11 @@ export const useRunInputDialog = ({ executeGraph({ graphId: flowID ?? "", graphVersion: flowVersion || null, - data: { inputs: inputValues, credentials_inputs: credentialValues }, + data: { + inputs: inputValues, + credentials_inputs: credentialValues, + source: "builder", + }, }); }; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts index 7a346d8d7b94..f801e1d958b8 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/useAgentRunModal.ts @@ -284,6 +284,7 @@ export function useAgentRunModal( data: { inputs: inputValues, credentials_inputs: inputCredentials, + source: "library", }, }); } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/useRunDetailHeader.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/useRunDetailHeader.ts index f90c495df64c..6aa69b5d2580 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/useRunDetailHeader.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/useRunDetailHeader.ts @@ -105,6 +105,7 @@ export function useRunDetailHeader( data: { inputs: (run as any).inputs || {}, credentials_inputs: (run as any).credential_inputs || {}, + source: "library", }, }); diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx index 869561a293c0..5ad5c9cbdd6f 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx @@ -151,6 +151,7 @@ export function AgentRunDetailsView({ graph.version, run.inputs!, run.credential_inputs!, + "library", ) .then(({ id }) => { analytics.sendDatafastEvent("run_agent", { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx index a037ca9645ea..16380c072ab5 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx @@ -194,7 +194,13 @@ export function AgentRunDraftView({ } // TODO: on executing preset with changes, ask for confirmation and offer save+run const newRun = await api - .executeGraph(graph.id, graph.version, inputValues, inputCredentials) + .executeGraph( + graph.id, + graph.version, + inputValues, + inputCredentials, + "library", + ) .catch(toastOnFail("execute agent")); if (newRun && onRun) onRun(newRun.id); @@ -204,8 +210,6 @@ export function AgentRunDraftView({ .then((newRun) => onRun && onRun(newRun.id)) .catch(toastOnFail("execute agent preset")); } - // Mark run agent onboarding step as completed - completeOnboardingStep("MARKETPLACE_RUN_AGENT"); analytics.sendDatafastEvent("run_agent", { name: graph.name, @@ -257,7 +261,6 @@ export function AgentRunDraftView({ onCreatePreset, toast, toastOnFail, - completeOnboardingStep, ]); const doUpdatePreset = useCallback(async () => { @@ -296,7 +299,6 @@ export function AgentRunDraftView({ onUpdatePreset, toast, toastOnFail, - completeOnboardingStep, ]); const doSetPresetActive = useCallback( @@ -343,7 +345,6 @@ export function AgentRunDraftView({ onCreatePreset, toast, toastOnFail, - completeOnboardingStep, ]); const openScheduleDialog = useCallback(() => { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-schedule-details-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-schedule-details-view.tsx index b90a4f24d6ca..414aa3863b66 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-schedule-details-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-schedule-details-view.tsx @@ -100,6 +100,7 @@ export function AgentScheduleDetailsView({ graph.version, schedule.input_data, schedule.input_credentials, + "library", ) .then((run) => onForcedRun(run.id)) .catch(toastOnFail("execute agent")), diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index b246040ed9c9..c50368b98cf0 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -6228,6 +6228,13 @@ }, "type": "object", "title": "Credentials Inputs" + }, + "source": { + "anyOf": [ + { "type": "string", "enum": ["builder", "library"] }, + { "type": "null" } + ], + "title": "Source" } }, "type": "object", @@ -6382,7 +6389,16 @@ "title": "CreateAPIKeyResponse" }, "CreateGraph": { - "properties": { "graph": { "$ref": "#/components/schemas/Graph" } }, + "properties": { + "graph": { "$ref": "#/components/schemas/Graph" }, + "source": { + "anyOf": [ + { "type": "string", "enum": ["builder", "upload"] }, + { "type": "null" } + ], + "title": "Source" + } + }, "type": "object", "required": ["graph"], "title": "CreateGraph" diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx index 2c1731a16f43..99e340087b9e 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx @@ -27,7 +27,6 @@ import { default as NextLink } from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; import { useQueryClient } from "@tanstack/react-query"; import { getGetV2ListLibraryAgentsQueryKey } from "@/app/api/__generated__/endpoints/library/library"; @@ -65,7 +64,6 @@ export default function useAgentGraph( ); const [xyNodes, setXYNodes] = useState([]); const [xyEdges, setXYEdges] = useState([]); - const { state, completeStep } = useOnboarding(); const betaBlocks = useGetFlag(Flag.BETA_BLOCKS); // Filter blocks based on beta flags @@ -826,6 +824,7 @@ export default function useAgentGraph( savedAgent.version, inputs, credentialsInputs, + "builder", ); setActiveExecutionID(graphExecution.id); @@ -836,10 +835,6 @@ export default function useAgentGraph( path.set("flowVersion", savedAgent.version.toString()); path.set("flowExecutionID", graphExecution.id); router.push(`${pathname}?${path.toString()}`); - - if (state?.completedSteps.includes("BUILDER_SAVE_AGENT")) { - completeStep("BUILDER_RUN_AGENT"); - } } catch (error) { // Check if this is a structured validation error from the backend if (error instanceof ApiError && error.isGraphValidationError()) { @@ -893,7 +888,6 @@ export default function useAgentGraph( searchParams, pathname, router, - state, isSaving, isRunning, processedUpdates, diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index f014b37442a9..21d90f4a67bf 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -229,7 +229,7 @@ export default class BackendAPI { createGraph( graph: GraphCreatable, - source?: "builder" | "upload", + source?: GraphCreationSource, ): Promise { const requestBody: GraphCreateRequestBody = { graph }; if (source) { @@ -258,11 +258,13 @@ export default class BackendAPI { version: number, inputs: { [key: string]: any } = {}, credentials_inputs: { [key: string]: CredentialsMetaInput } = {}, + source?: GraphExecutionSource, ): Promise { - return this._request("POST", `/graphs/${id}/execute/${version}`, { - inputs, - credentials_inputs, - }); + const body: GraphExecuteRequestBody = { inputs, credentials_inputs }; + if (source) { + body.source = source; + } + return this._request("POST", `/graphs/${id}/execute/${version}`, body); } getExecutions(): Promise { @@ -1295,9 +1297,18 @@ declare global { /* *** UTILITY TYPES *** */ +type GraphCreationSource = "builder" | "upload"; +type GraphExecutionSource = "builder" | "library" | "onboarding"; + type GraphCreateRequestBody = { graph: GraphCreatable; - source?: "builder" | "upload"; + source?: GraphCreationSource; +}; + +type GraphExecuteRequestBody = { + inputs: { [key: string]: any }; + credentials_inputs: { [key: string]: CredentialsMetaInput }; + source?: GraphExecutionSource; }; type WebsocketMessageTypeMap = { From 7f3efd9dfb4dc546a2c5a73f877457dc088bab92 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 13 Nov 2025 11:49:51 +0900 Subject: [PATCH 24/33] Backend `RE_RUN_AGENT` --- .../backend/backend/data/onboarding.py | 30 +++++++++++++++---- .../backend/backend/server/routers/v1.py | 6 ++-- .../OldAgentLibraryView.tsx | 2 -- .../components/agent-run-details-view.tsx | 4 --- .../components/agent-run-draft-view.tsx | 9 ------ 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index f59f69af7609..011d7e8fe3a0 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -17,11 +17,12 @@ NotificationEvent, ) from backend.data.user import get_user_by_id -from backend.server.model import NotificationPayload, OnboardingNotificationPayload +from backend.server.model import OnboardingNotificationPayload from backend.server.v2.store.model import StoreAgentDetails from backend.util.cache import cached from backend.util.json import SafeJson from backend.util.timezone_utils import get_user_timezone_or_utc +from backend.data import execution as execution_db # Mapping from user reason id to categories to search for when choosing agent to show REASON_MAPPING: dict[str, list[str]] = { @@ -43,8 +44,6 @@ OnboardingStep.AGENT_INPUT, OnboardingStep.CONGRATS, OnboardingStep.MARKETPLACE_VISIT, - OnboardingStep.RE_RUN_AGENT, # <- - OnboardingStep.RE_RUN_AGENT, OnboardingStep.BUILDER_OPEN, ] @@ -203,7 +202,26 @@ async def _send_onboarding_notification( ) -def clean_and_split(text: str) -> list[str]: +async def complete_re_run_agent( + user_id: str, graph_id: str +) -> None: + """ + Complete RE_RUN_AGENT step when a user runs a graph they've run before. + Keeps overhead low by only counting executions if the step is still pending. + """ + onboarding = await get_user_onboarding(user_id) + if OnboardingStep.RE_RUN_AGENT in onboarding.completedSteps: + return + + # Includes current execution, so count > 1 means there was at least one prior run. + previous_exec_count = await execution_db.get_graph_executions_count( + user_id=user_id, graph_id=graph_id + ) + if previous_exec_count > 1: + await complete_onboarding_step(user_id, OnboardingStep.RE_RUN_AGENT) + + +def _clean_and_split(text: str) -> list[str]: """ Removes all special characters from a string, truncates it to 100 characters, and splits it by whitespace and commas. @@ -362,7 +380,7 @@ async def increment_runs(user_id: str): await complete_onboarding_step(user_id, step) -async def complete_get_results_if_applicable(user_id: str, graph_exec_id: str) -> None: +async def complete_get_results(user_id: str, graph_exec_id: str) -> None: onboarding = await get_user_onboarding(user_id) if ( onboarding.onboardingAgentExecutionId == graph_exec_id @@ -377,7 +395,7 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: where_clause: dict[str, Any] = {} - custom = clean_and_split((user_onboarding.usageReason or "").lower()) + custom = _clean_and_split((user_onboarding.usageReason or "").lower()) if categories: where_clause["OR"] = [ diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 25e054c790a3..4d5c6f69e091 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -54,7 +54,8 @@ UserOnboardingUpdate, OnboardingStep, complete_onboarding_step, - complete_get_results_if_applicable, + complete_get_results, + complete_re_run_agent, increment_runs, get_recommended_agents, get_user_onboarding, @@ -937,6 +938,7 @@ async def execute_graph( record_graph_execution(graph_id=graph_id, status="success", user_id=user_id) record_graph_operation(operation="execute", status="success") await increment_runs(user_id) + await complete_re_run_agent(user_id, graph_id) if source == "library": await complete_onboarding_step(user_id, OnboardingStep.MARKETPLACE_RUN_AGENT) elif source == "builder": @@ -1074,7 +1076,7 @@ async def get_graph_execution( ) try: - await complete_get_results_if_applicable(user_id, graph_exec_id) + await complete_get_results(user_id, graph_exec_id) except Exception: logger.warning( "Failed to auto-complete GET_RESULTS for user %s exec %s", diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx index a7fdb0a9ff85..dff1b5a7bb80 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/OldAgentLibraryView.tsx @@ -532,7 +532,6 @@ export function OldAgentLibraryView() { onCreateSchedule={onCreateSchedule} onCreatePreset={onCreatePreset} agentActions={agentActions} - runCount={agentRuns.length} recommendedScheduleCron={agent?.recommended_schedule_cron || null} /> ) : selectedView.type == "preset" ? ( @@ -548,7 +547,6 @@ export function OldAgentLibraryView() { onUpdatePreset={onUpdatePreset} doDeletePreset={setConfirmingDeleteAgentPreset} agentActions={agentActions} - runCount={agentRuns.length} /> ) : selectedView.type == "schedule" ? ( selectedSchedule && diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx index 5ad5c9cbdd6f..b497c2eb445b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx @@ -38,7 +38,6 @@ import { AgentRunStatus, agentRunStatusMap } from "./agent-run-status-chip"; import useCredits from "@/hooks/useCredits"; import { AgentRunOutputView } from "./agent-run-output-view"; import { analytics } from "@/services/analytics"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; export function AgentRunDetailsView({ agent, @@ -65,8 +64,6 @@ export function AgentRunDetailsView({ [run], ); - const { completeStep } = useOnboarding(); - const toastOnFail = useToastOnFail(); const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => { @@ -158,7 +155,6 @@ export function AgentRunDetailsView({ name: graph.name, id: graph.id, }); - completeStep("RE_RUN_AGENT"); onRun(id); }) .catch(toastOnFail("execute agent")); diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx index 16380c072ab5..ac72d794f69b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx @@ -42,7 +42,6 @@ import { } from "@/components/molecules/Toast/use-toast"; import { AgentStatus, AgentStatusChip } from "./agent-status-chip"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; import { analytics } from "@/services/analytics"; export function AgentRunDraftView({ @@ -56,7 +55,6 @@ export function AgentRunDraftView({ doCreateSchedule: _doCreateSchedule, onCreateSchedule, agentActions, - runCount, className, recommendedScheduleCron, }: { @@ -75,7 +73,6 @@ export function AgentRunDraftView({ credentialsInputs: Record, ) => Promise; onCreateSchedule?: (schedule: Schedule) => void; - runCount: number; className?: string; } & ( | { @@ -104,7 +101,6 @@ export function AgentRunDraftView({ const [changedPresetAttributes, setChangedPresetAttributes] = useState< Set >(new Set()); - const { completeStep: completeOnboardingStep } = useOnboarding(); const [cronScheduleDialogOpen, setCronScheduleDialogOpen] = useState(false); // Update values if agentPreset parameter is changed @@ -215,10 +211,6 @@ export function AgentRunDraftView({ name: graph.name, id: graph.id, }); - - if (runCount > 0) { - completeOnboardingStep("RE_RUN_AGENT"); - } }, [ api, graph, @@ -226,7 +218,6 @@ export function AgentRunDraftView({ inputCredentials, onRun, toastOnFail, - completeOnboardingStep, ]); const doCreatePreset = useCallback(async () => { From 7ab166092fb0b2af5156bf503a7ffda35a4246cb Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 13 Nov 2025 19:27:33 +0900 Subject: [PATCH 25/33] Format --- .../backend/backend/data/onboarding.py | 6 ++-- .../backend/server/integrations/router.py | 6 +++- .../backend/backend/server/routers/v1.py | 22 +++++++++------ .../server/v2/library/routes/agents.py | 5 ++-- .../server/v2/library/routes/presets.py | 3 +- .../onboarding/5-run/useOnboardingRunStep.tsx | 8 ++++-- .../onboarding/6-congrats/actions.ts | 8 ++++-- .../components/agent-run-draft-view.tsx | 9 +----- .../components/cron-scheduler-dialog.tsx | 1 - .../frontend/src/app/api/helpers.ts | 28 +++++++++---------- .../src/lib/autogpt-server-api/client.ts | 1 - .../src/providers/onboarding/helpers.ts | 28 ++++++++++++++----- .../onboarding/onboarding-provider.tsx | 23 +++++++++------ 13 files changed, 84 insertions(+), 64 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 011d7e8fe3a0..de8504531d3e 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -9,6 +9,7 @@ from prisma.models import UserOnboarding from prisma.types import UserOnboardingCreateInput, UserOnboardingUpdateInput +from backend.data import execution as execution_db from backend.data.block import get_blocks from backend.data.credit import get_user_credit_model from backend.data.model import CredentialsMetaInput @@ -22,7 +23,6 @@ from backend.util.cache import cached from backend.util.json import SafeJson from backend.util.timezone_utils import get_user_timezone_or_utc -from backend.data import execution as execution_db # Mapping from user reason id to categories to search for when choosing agent to show REASON_MAPPING: dict[str, list[str]] = { @@ -202,9 +202,7 @@ async def _send_onboarding_notification( ) -async def complete_re_run_agent( - user_id: str, graph_id: str -) -> None: +async def complete_re_run_agent(user_id: str, graph_id: str) -> None: """ Complete RE_RUN_AGENT step when a user runs a graph they've run before. Keeps overhead low by only counting executions if the step is still pending. diff --git a/autogpt_platform/backend/backend/server/integrations/router.py b/autogpt_platform/backend/backend/server/integrations/router.py index 3ab5fd1b2194..b4227ad02acf 100644 --- a/autogpt_platform/backend/backend/server/integrations/router.py +++ b/autogpt_platform/backend/backend/server/integrations/router.py @@ -33,7 +33,11 @@ OAuth2Credentials, UserIntegrations, ) -from backend.data.onboarding import OnboardingStep, complete_onboarding_step, increment_runs +from backend.data.onboarding import ( + OnboardingStep, + complete_onboarding_step, + increment_runs, +) from backend.data.user import get_user_integrations from backend.executor.utils import add_graph_execution from backend.integrations.ayrshare import AyrshareClient, SocialPlatform diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 935acc9f9ecf..ffe51e1a602d 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -24,12 +24,11 @@ UploadFile, ) from fastapi.concurrency import run_in_threadpool +from prisma.models import UserOnboarding from pydantic import BaseModel from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND from typing_extensions import Optional, TypedDict -from prisma.models import UserOnboarding -from backend.server.v2.store.model import StoreAgentDetails import backend.server.integrations.router import backend.server.routers.analytics import backend.server.v2.library.db as library_db @@ -51,14 +50,14 @@ from backend.data.notifications import NotificationPreference, NotificationPreferenceDTO from backend.data.onboarding import ( FrontendOnboardingStep, - UserOnboardingUpdate, OnboardingStep, - complete_onboarding_step, + UserOnboardingUpdate, complete_get_results, + complete_onboarding_step, complete_re_run_agent, - increment_runs, get_recommended_agents, get_user_onboarding, + increment_runs, onboarding_enabled, reset_user_onboarding, update_user_onboarding, @@ -94,6 +93,7 @@ UpdateTimezoneRequest, UploadFileResponse, ) +from backend.server.v2.store.model import StoreAgentDetails from backend.util.cache import cached from backend.util.clients import get_scheduler_client from backend.util.cloud_storage import get_cloud_storage_handler @@ -265,7 +265,9 @@ async def update_preferences( tags=["onboarding"], dependencies=[Security(requires_user)], ) -async def get_onboarding(user_id: Annotated[str, Security(get_user_id)]) -> UserOnboarding: +async def get_onboarding( + user_id: Annotated[str, Security(get_user_id)] +) -> UserOnboarding: return await get_user_onboarding(user_id) @@ -323,7 +325,9 @@ async def is_onboarding_enabled() -> bool: tags=["onboarding"], dependencies=[Security(requires_user)], ) -async def reset_onboarding(user_id: Annotated[str, Security(get_user_id)]) -> UserOnboarding: +async def reset_onboarding( + user_id: Annotated[str, Security(get_user_id)] +) -> UserOnboarding: return await reset_user_onboarding(user_id) @@ -974,7 +978,9 @@ async def execute_graph( await increment_runs(user_id) await complete_re_run_agent(user_id, graph_id) if source == "library": - await complete_onboarding_step(user_id, OnboardingStep.MARKETPLACE_RUN_AGENT) + await complete_onboarding_step( + user_id, OnboardingStep.MARKETPLACE_RUN_AGENT + ) elif source == "builder": await complete_onboarding_step(user_id, OnboardingStep.BUILDER_RUN_AGENT) return result diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py index 9f1de9ec8ed2..658e20c25747 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py @@ -1,16 +1,15 @@ import logging from typing import Optional -from prisma.enums import OnboardingStep - import autogpt_libs.auth as autogpt_auth_lib from fastapi import APIRouter, Body, HTTPException, Query, Security, status from fastapi.responses import Response +from prisma.enums import OnboardingStep -from backend.data.onboarding import complete_onboarding_step import backend.server.v2.library.db as library_db import backend.server.v2.library.model as library_model import backend.server.v2.store.exceptions as store_exceptions +from backend.data.onboarding import complete_onboarding_step from backend.util.exceptions import DatabaseError, NotFoundError logger = logging.getLogger(__name__) diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py index c68474ac5b7c..b1810395f05d 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py @@ -1,8 +1,6 @@ import logging from typing import Any, Optional -from backend.data.onboarding import increment_runs - import autogpt_libs.auth as autogpt_auth_lib from fastapi import APIRouter, Body, HTTPException, Query, Security, status @@ -12,6 +10,7 @@ from backend.data.graph import get_graph from backend.data.integrations import get_webhook from backend.data.model import CredentialsMetaInput +from backend.data.onboarding import increment_runs from backend.executor.utils import add_graph_execution, make_node_credentials_input_map from backend.integrations.creds_manager import IntegrationCredentialsManager from backend.integrations.webhooks import get_webhook_manager diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx index 812d50b95db5..663bbec0fb30 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx @@ -109,9 +109,11 @@ export function useOnboardingRunStep() { setRunningAgent(true); try { - const libraryAgent = await resolveResponse(postV2AddMarketplaceAgent({ - store_listing_version_id: storeAgent?.store_listing_version_id || "", - })); + const libraryAgent = await resolveResponse( + postV2AddMarketplaceAgent({ + store_listing_version_id: storeAgent?.store_listing_version_id || "", + }), + ); const { id: runID } = await api.executeGraph( libraryAgent.graph_id as GraphID, diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts index a77f6a0ee255..9875406a26e5 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts @@ -11,9 +11,11 @@ export async function finishOnboarding() { const listingId = onboarding?.selectedStoreListingVersionId; if (listingId) { - const data = await resolveResponse(postV2AddMarketplaceAgent({ - store_listing_version_id: listingId, - })); + const data = await resolveResponse( + postV2AddMarketplaceAgent({ + store_listing_version_id: listingId, + }), + ); revalidatePath(`/library/agents/${data.id}`, "layout"); redirect(`/library/agents/${data.id}`); } else { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx index ac72d794f69b..13faca2f08e5 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx @@ -211,14 +211,7 @@ export function AgentRunDraftView({ name: graph.name, id: graph.id, }); - }, [ - api, - graph, - inputValues, - inputCredentials, - onRun, - toastOnFail, - ]); + }, [api, graph, inputValues, inputCredentials, onRun, toastOnFail]); const doCreatePreset = useCallback(async () => { if (!onCreatePreset) return; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx index ca8a5fe624b6..e998823a89ad 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog.tsx @@ -7,7 +7,6 @@ import { Dialog } from "@/components/molecules/Dialog/Dialog"; import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth"; import { getTimezoneDisplayName } from "@/lib/timezone-utils"; import { InfoIcon } from "lucide-react"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; // Base type for cron expression only type CronOnlyCallback = (cronExpression: string) => void; diff --git a/autogpt_platform/frontend/src/app/api/helpers.ts b/autogpt_platform/frontend/src/app/api/helpers.ts index d85b01a3f063..2ed45c9517ff 100644 --- a/autogpt_platform/frontend/src/app/api/helpers.ts +++ b/autogpt_platform/frontend/src/app/api/helpers.ts @@ -1,5 +1,7 @@ -import BackendAPI from "@/lib/autogpt-server-api"; -import { getV1IsOnboardingEnabled, getV1OnboardingState } from "./__generated__/endpoints/onboarding/onboarding"; +import { + getV1IsOnboardingEnabled, + getV1OnboardingState, +} from "./__generated__/endpoints/onboarding/onboarding"; /** * Narrow an orval response to its success payload if and only if it is a `200` status with OK shape. @@ -31,17 +33,17 @@ type ResponseWithData = { status: number; data: unknown }; type ExtractResponseData = T extends { data: infer D; } -? D -: never; + ? D + : never; type SuccessfulResponses = T extends { status: infer S; } -? S extends number -? `${S}` extends `2${string}` -? T -: never -: never -: never; + ? S extends number + ? `${S}` extends `2${string}` + ? T + : never + : never + : never; /** * Resolve an Orval response to its payload after asserting the status is either the explicit @@ -69,10 +71,7 @@ export function resolveResponse( export async function resolveResponse< TSuccess extends ResponseWithData, TCode extends number, ->( - promise: Promise, - expected?: TCode, -) { +>(promise: Promise, expected?: TCode) { const res = await promise; const isSuccessfulStatus = typeof res.status === "number" && res.status >= 200 && res.status < 300; @@ -89,7 +88,6 @@ export async function resolveResponse< } export async function shouldShowOnboarding() { - const api = new BackendAPI(); const isEnabled = await resolveResponse(getV1IsOnboardingEnabled()); const onboarding = await resolveResponse(getV1OnboardingState()); const isCompleted = onboarding.completedSteps.includes("CONGRATS"); diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 6e0c73e4a80b..228566668799 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -64,7 +64,6 @@ import type { SubmissionStatus, TransactionHistory, User, - UserOnboarding, UserPasswordCredentials, UsersBalanceHistoryResponse, WebSocketNotification, diff --git a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts index 3b8a52793a5a..6a05315383a4 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/helpers.ts +++ b/autogpt_platform/frontend/src/providers/onboarding/helpers.ts @@ -7,7 +7,11 @@ import { UserOnboarding as RawUserOnboarding } from "@/app/api/__generated__/mod export type LocalOnboardingStateUpdate = Omit< Partial, - "completedSteps" | "rewardedFor" | "lastRunAt" | "consecutiveRunDays" | "agentRuns" + | "completedSteps" + | "rewardedFor" + | "lastRunAt" + | "consecutiveRunDays" + | "agentRuns" >; export function fromBackendUserOnboarding( @@ -17,9 +21,12 @@ export function fromBackendUserOnboarding( ...onboarding, usageReason: onboarding.usageReason || null, otherIntegrations: onboarding.otherIntegrations || null, - selectedStoreListingVersionId: onboarding.selectedStoreListingVersionId || null, - agentInput: onboarding.agentInput as {} || null, - onboardingAgentExecutionId: onboarding.onboardingAgentExecutionId as GraphExecutionID || null, + selectedStoreListingVersionId: + onboarding.selectedStoreListingVersionId || null, + agentInput: + (onboarding.agentInput as Record) || null, + onboardingAgentExecutionId: + (onboarding.onboardingAgentExecutionId as GraphExecutionID) || null, lastRunAt: onboarding.lastRunAt ? new Date(onboarding.lastRunAt) : null, }; } @@ -45,10 +52,17 @@ export function updateOnboardingState( rewardedFor: prevState?.rewardedFor ?? [], usageReason: newState.usageReason ?? prevState?.usageReason ?? null, integrations: newState.integrations ?? prevState?.integrations ?? [], - otherIntegrations: newState.otherIntegrations ?? prevState?.otherIntegrations ?? null, - selectedStoreListingVersionId: newState.selectedStoreListingVersionId ?? prevState?.selectedStoreListingVersionId ?? null, + otherIntegrations: + newState.otherIntegrations ?? prevState?.otherIntegrations ?? null, + selectedStoreListingVersionId: + newState.selectedStoreListingVersionId ?? + prevState?.selectedStoreListingVersionId ?? + null, agentInput: newState.agentInput ?? prevState?.agentInput ?? null, - onboardingAgentExecutionId: newState.onboardingAgentExecutionId ?? prevState?.onboardingAgentExecutionId ?? null, + onboardingAgentExecutionId: + newState.onboardingAgentExecutionId ?? + prevState?.onboardingAgentExecutionId ?? + null, lastRunAt: prevState?.lastRunAt ?? null, consecutiveRunDays: prevState?.consecutiveRunDays ?? 0, agentRuns: prevState?.agentRuns ?? 0, diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index e790a1cfd84e..ccf2a39773eb 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -11,7 +11,6 @@ import { import { useToast } from "@/components/molecules/Toast/use-toast"; import { useOnboardingTimezoneDetection } from "@/hooks/useOnboardingTimezoneDetection"; import { - OnboardingStep, UserOnboarding, WebSocketNotification, } from "@/lib/autogpt-server-api"; @@ -35,7 +34,12 @@ import { LocalOnboardingStateUpdate, } from "./helpers"; import { resolveResponse } from "@/app/api/helpers"; -import { getV1IsOnboardingEnabled, getV1OnboardingState, patchV1UpdateOnboardingState, postV1CompleteOnboardingStep } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { + getV1IsOnboardingEnabled, + getV1OnboardingState, + patchV1UpdateOnboardingState, + postV1CompleteOnboardingStep, +} from "@/app/api/__generated__/endpoints/onboarding/onboarding"; import { PostV1CompleteOnboardingStepStep } from "@/app/api/__generated__/models/postV1CompleteOnboardingStepStep"; type FrontendOnboardingStep = PostV1CompleteOnboardingStepStep; @@ -51,7 +55,10 @@ const OnboardingContext = createContext< | undefined >(undefined); -export function useOnboarding(step?: number, completeStep?: FrontendOnboardingStep) { +export function useOnboarding( + step?: number, + completeStep?: FrontendOnboardingStep, +) { const context = useContext(OnboardingContext); if (!context) @@ -149,10 +156,7 @@ export default function OnboardingProvider({ // Handle redirects for completed onboarding if ( isOnOnboardingRoute && - shouldRedirectFromOnboarding( - onboarding.completedSteps, - pathname, - ) + shouldRedirectFromOnboarding(onboarding.completedSteps, pathname) ) { router.push("/marketplace"); } @@ -182,7 +186,10 @@ export default function OnboardingProvider({ } fetchOnboarding().catch((error) => { - console.error("Failed to refresh onboarding after notification:", error); + console.error( + "Failed to refresh onboarding after notification:", + error, + ); }); }, [fetchOnboarding], From 1e731c0ae22c39a213e73c536a85190234230b42 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Fri, 14 Nov 2025 11:07:55 +0900 Subject: [PATCH 26/33] Increment runs for scheduled agents --- autogpt_platform/backend/backend/executor/scheduler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autogpt_platform/backend/backend/executor/scheduler.py b/autogpt_platform/backend/backend/executor/scheduler.py index f055ec079df1..7a7fe4f5e4d4 100644 --- a/autogpt_platform/backend/backend/executor/scheduler.py +++ b/autogpt_platform/backend/backend/executor/scheduler.py @@ -25,6 +25,7 @@ from backend.data.block import BlockInput from backend.data.execution import GraphExecutionWithNodes from backend.data.model import CredentialsMetaInput +from backend.data.onboarding import increment_runs from backend.executor import utils as execution_utils from backend.monitoring import ( NotificationJobArgs, @@ -150,6 +151,7 @@ async def _execute_graph(**kwargs): inputs=args.input_data, graph_credentials_inputs=args.input_credentials, ) + await increment_runs(args.user_id) elapsed = asyncio.get_event_loop().time() - start_time logger.info( f"Graph execution started with ID {graph_exec.id} for graph {args.graph_id} " From 154135fa4b4606136b3759a65a55822b13fad29e Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 17 Nov 2025 11:32:24 +0900 Subject: [PATCH 27/33] Fixes --- .../backend/backend/data/notification_bus.py | 7 ++++- .../backend/backend/data/onboarding.py | 11 +------ .../backend/backend/server/model.py | 5 +++- .../backend/backend/server/routers/v1.py | 30 ++++++++++--------- .../server/v2/library/routes/agents.py | 10 +++++-- .../onboarding/5-run/useOnboardingRunStep.tsx | 1 + .../onboarding/6-congrats/actions.ts | 1 + .../legacy-builder/RunnerInputUI.tsx | 1 - .../components/agent-run-details-view.tsx | 4 --- .../frontend/src/app/api/openapi.json | 23 +++++++------- .../Navbar/components/Wallet/Wallet.tsx | 12 ++++---- .../Wallet/components/WalletTaskGroups.tsx | 14 +++++---- .../onboarding/onboarding-provider.tsx | 20 ++++++++----- 13 files changed, 77 insertions(+), 62 deletions(-) diff --git a/autogpt_platform/backend/backend/data/notification_bus.py b/autogpt_platform/backend/backend/data/notification_bus.py index ddd0681a2c13..6eb90dca1270 100644 --- a/autogpt_platform/backend/backend/data/notification_bus.py +++ b/autogpt_platform/backend/backend/data/notification_bus.py @@ -2,7 +2,7 @@ from typing import AsyncGenerator -from pydantic import BaseModel +from pydantic import BaseModel, field_serializer from backend.data.event_bus import AsyncRedisEventBus from backend.server.model import NotificationPayload @@ -15,6 +15,11 @@ class NotificationEvent(BaseModel): user_id: str payload: NotificationPayload + @field_serializer("payload") + def serialize_payload(self, payload: NotificationPayload): + """Ensure extra fields survive Redis serialization.""" + return payload.model_dump() + class AsyncRedisNotificationEventBus(AsyncRedisEventBus[NotificationEvent]): Model = NotificationEvent # type: ignore diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index de8504531d3e..c672a626a1fe 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -195,7 +195,7 @@ async def _send_onboarding_notification( payload = OnboardingNotificationPayload( type="onboarding", event=event, - step=step.value, + step=step, ) await AsyncRedisNotificationEventBus().publish( NotificationEvent(user_id=user_id, payload=payload) @@ -378,15 +378,6 @@ async def increment_runs(user_id: str): await complete_onboarding_step(user_id, step) -async def complete_get_results(user_id: str, graph_exec_id: str) -> None: - onboarding = await get_user_onboarding(user_id) - if ( - onboarding.onboardingAgentExecutionId == graph_exec_id - and OnboardingStep.GET_RESULTS not in onboarding.completedSteps - ): - await complete_onboarding_step(user_id, OnboardingStep.GET_RESULTS) - - async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: user_onboarding = await get_user_onboarding(user_id) categories = REASON_MAPPING.get(user_onboarding.usageReason or "", []) diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index 1f627362c367..b5fbee23cf95 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -2,6 +2,7 @@ from typing import Any, Literal, Optional import pydantic +from prisma.enums import OnboardingStep from backend.data.api_key import APIKeyInfo, APIKeyPermission from backend.data.graph import Graph @@ -88,6 +89,8 @@ class NotificationPayload(pydantic.BaseModel): type: str event: str + model_config = pydantic.ConfigDict(extra="allow") + class OnboardingNotificationPayload(NotificationPayload): - step: str + step: OnboardingStep diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index ffe51e1a602d..1d3ad6f8bc42 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -52,7 +52,6 @@ FrontendOnboardingStep, OnboardingStep, UserOnboardingUpdate, - complete_get_results, complete_onboarding_step, complete_re_run_agent, get_recommended_agents, @@ -949,9 +948,7 @@ async def execute_graph( credentials_inputs: Annotated[ dict[str, CredentialsMetaInput], Body(..., embed=True, default_factory=dict) ], - source: Annotated[ - GraphExecutionSource | None, Body(default=None, embed=True) - ] = None, + source: Annotated[GraphExecutionSource | None, Body(embed=True)] = None, graph_version: Optional[int] = None, preset_id: Optional[str] = None, ) -> execution_db.GraphExecutionMeta: @@ -1096,6 +1093,15 @@ async def list_graph_executions( filtered_executions = await hide_activity_summaries_if_disabled( paginated_result.executions, user_id ) + onboarding = await get_user_onboarding(user_id) + if ( + onboarding.onboardingAgentExecutionId + and onboarding.onboardingAgentExecutionId + in [exec.id for exec in filtered_executions] + and OnboardingStep.GET_RESULTS not in onboarding.completedSteps + ): + await complete_onboarding_step(user_id, OnboardingStep.GET_RESULTS) + return execution_db.GraphExecutionsPaginated( executions=filtered_executions, pagination=paginated_result.pagination ) @@ -1130,16 +1136,12 @@ async def get_graph_execution( # Apply feature flags to filter out disabled features result = await hide_activity_summary_if_disabled(result, user_id) - - try: - await complete_get_results(user_id, graph_exec_id) - except Exception: - logger.warning( - "Failed to auto-complete GET_RESULTS for user %s exec %s", - user_id, - graph_exec_id, - exc_info=True, - ) + onboarding = await get_user_onboarding(user_id) + if ( + onboarding.onboardingAgentExecutionId == graph_exec_id + and OnboardingStep.GET_RESULTS not in onboarding.completedSteps + ): + await complete_onboarding_step(user_id, OnboardingStep.GET_RESULTS) return result diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py index 658e20c25747..4681cf17c4b7 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py @@ -1,5 +1,5 @@ import logging -from typing import Optional +from typing import Literal, Optional import autogpt_libs.auth as autogpt_auth_lib from fastapi import APIRouter, Body, HTTPException, Query, Security, status @@ -195,6 +195,9 @@ async def get_library_agent_by_store_listing_version_id( ) async def add_marketplace_agent_to_library( store_listing_version_id: str = Body(embed=True), + source: Literal["onboarding", "marketplace"] = Body( + default="marketplace", embed=True + ), user_id: str = Security(autogpt_auth_lib.get_user_id), ) -> library_model.LibraryAgent: """ @@ -216,7 +219,10 @@ async def add_marketplace_agent_to_library( store_listing_version_id=store_listing_version_id, user_id=user_id, ) - await complete_onboarding_step(user_id, OnboardingStep.MARKETPLACE_ADD_AGENT) + if source != "onboarding": + await complete_onboarding_step( + user_id, OnboardingStep.MARKETPLACE_ADD_AGENT + ) return agent except store_exceptions.AgentNotFoundError as e: diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx index 663bbec0fb30..37538a21913a 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/useOnboardingRunStep.tsx @@ -112,6 +112,7 @@ export function useOnboardingRunStep() { const libraryAgent = await resolveResponse( postV2AddMarketplaceAgent({ store_listing_version_id: storeAgent?.store_listing_version_id || "", + source: "onboarding", }), ); diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts index 9875406a26e5..f36b86d9d82a 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/actions.ts @@ -14,6 +14,7 @@ export async function finishOnboarding() { const data = await resolveResponse( postV2AddMarketplaceAgent({ store_listing_version_id: listingId, + source: "onboarding", }), ); revalidatePath(`/library/agents/${data.id}`, "layout"); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/RunnerInputUI.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/RunnerInputUI.tsx index 21d93f6fe02c..bff21c46f276 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/RunnerInputUI.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/RunnerInputUI.tsx @@ -83,7 +83,6 @@ export function RunnerInputDialog({ onRun={doRun ? undefined : doClose} doCreateSchedule={doCreateSchedule ? handleSchedule : undefined} onCreateSchedule={doCreateSchedule ? undefined : doClose} - runCount={0} /> diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx index 89223ccab96b..cfc2a30923a6 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-details-view.tsx @@ -38,7 +38,6 @@ import { AgentRunStatus, agentRunStatusMap } from "./agent-run-status-chip"; import useCredits from "@/hooks/useCredits"; import { AgentRunOutputView } from "./agent-run-output-view"; import { analytics } from "@/services/analytics"; -import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; export function AgentRunDetailsView({ agent, @@ -65,8 +64,6 @@ export function AgentRunDetailsView({ [run], ); - const { completeStep } = useOnboarding(); - const toastOnFail = useToastOnFail(); const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => { @@ -158,7 +155,6 @@ export function AgentRunDetailsView({ name: graph.name, id: graph.id, }); - completeStep("RE_RUN_AGENT"); onRun(id); }) .catch(toastOnFail("execute agent")); diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index f887153a71cb..edec0bd507ae 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -900,18 +900,8 @@ "AGENT_NEW_RUN", "AGENT_INPUT", "CONGRATS", - "GET_RESULTS", "MARKETPLACE_VISIT", - "MARKETPLACE_ADD_AGENT", - "MARKETPLACE_RUN_AGENT", - "BUILDER_SAVE_AGENT", - "RE_RUN_AGENT", - "RUN_AGENTS", - "RUN_3_DAYS", - "RUN_14_DAYS", - "RUN_AGENTS_100", - "BUILDER_OPEN", - "BUILDER_RUN_AGENT" + "BUILDER_OPEN" ], "type": "string", "title": "Step" @@ -6273,7 +6263,10 @@ }, "source": { "anyOf": [ - { "type": "string", "enum": ["builder", "library"] }, + { + "type": "string", + "enum": ["builder", "library", "onboarding"] + }, { "type": "null" } ], "title": "Source" @@ -6324,6 +6317,12 @@ "store_listing_version_id": { "type": "string", "title": "Store Listing Version Id" + }, + "source": { + "type": "string", + "enum": ["onboarding", "marketplace"], + "title": "Source", + "default": "marketplace" } }, "type": "object", diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx index 9425e702f792..0a3c7de6c8db 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/Wallet.tsx @@ -14,7 +14,6 @@ import { } from "@/lib/autogpt-server-api"; import { cn } from "@/lib/utils"; import { useOnboarding } from "@/providers/onboarding/onboarding-provider"; -import { useBackendAPI } from "@/lib/autogpt-server-api/context"; import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag"; import { storage, Key as StorageKey } from "@/services/storage/local-storage"; import { WalletIcon } from "@phosphor-icons/react"; @@ -252,18 +251,21 @@ export function Wallet() { ); // React to onboarding notifications emitted by the provider - const api = useBackendAPI(); - const handleNotification = useCallback( (notification: WebSocketNotification) => { if ( notification.type !== "onboarding" || - notification.event !== "step_completed" + notification.event !== "step_completed" || + !walletRef.current ) { return; } - if (!walletRef.current) { + // Only trigger confetti for tasks that are in groups + const taskIds = groups + .flatMap((group) => group.tasks) + .map((task) => task.id); + if (!taskIds.includes(notification.step as OnboardingStep)) { return; } diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx index 669635a749ab..9b8471493b6c 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletTaskGroups.tsx @@ -16,7 +16,10 @@ export function TaskGroups({ groups }: Props) { const [openGroups, setOpenGroups] = useState>(() => { const initialState: Record = {}; groups.forEach((group) => { - initialState[group.name] = true; + const completed = group.tasks.every((task) => + state?.completedSteps?.includes(task.id), + ); + initialState[group.name] = !completed; }); return initialState; }); @@ -62,7 +65,7 @@ export function TaskGroups({ groups }: Props) { {} as Record, ), ); - }, [state?.completedSteps, isGroupCompleted]); + }, [state?.completedSteps, isGroupCompleted, groups]); const setRef = (name: string) => (el: HTMLDivElement | null) => { if (el) { @@ -101,9 +104,10 @@ export function TaskGroups({ groups }: Props) { useEffect(() => { groups.forEach((group) => { const groupCompleted = isGroupCompleted(group); - // Check if the last task in the group is completed - const alreadyCelebrated = state?.notified.includes( - group.tasks[group.tasks.length - 1].id, + // Check if all tasks in the group were already celebrated + // last task completed triggers group completion + const alreadyCelebrated = group.tasks.every((task) => + state?.notified.includes(task.id), ); if (groupCompleted) { diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx index ccf2a39773eb..ebd972d1bde9 100644 --- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx @@ -177,7 +177,7 @@ export default function OnboardingProvider({ const handleOnboardingNotification = useCallback( (notification: WebSocketNotification) => { - if (notification.type !== "onboarding") { + if (!isLoggedIn || notification.type !== "onboarding") { return; } @@ -192,7 +192,7 @@ export default function OnboardingProvider({ ); }); }, - [fetchOnboarding], + [fetchOnboarding, isLoggedIn], ); useEffect(() => { @@ -201,15 +201,21 @@ export default function OnboardingProvider({ handleOnboardingNotification, ); - api.connectWebSocket(); + if (isLoggedIn) { + api.connectWebSocket(); + } return () => { detachMessage(); }; - }, [api, handleOnboardingNotification]); + }, [api, handleOnboardingNotification, isLoggedIn]); const updateState = useCallback( (newState: LocalOnboardingStateUpdate) => { + if (!isLoggedIn) { + return; + } + setState((prev) => updateOnboardingState(prev, newState)); const updatePromise = (async () => { @@ -232,12 +238,12 @@ export default function OnboardingProvider({ pendingUpdatesRef.current.delete(updatePromise); }); }, - [toast], + [toast, isLoggedIn, fetchOnboarding, api, setState], ); const completeStep = useCallback( (step: FrontendOnboardingStep) => { - if (state?.completedSteps?.includes(step)) { + if (!isLoggedIn || state?.completedSteps?.includes(step)) { return; } @@ -262,7 +268,7 @@ export default function OnboardingProvider({ pendingUpdatesRef.current.delete(completionPromise); }); }, - [state?.completedSteps, fetchOnboarding, toast], + [isLoggedIn, state?.completedSteps, fetchOnboarding, toast], ); return ( From 05866d0f3c3f84346633a8c0067e7e2306921cd1 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Mon, 17 Nov 2025 20:02:59 +0900 Subject: [PATCH 28/33] Fix --- .../backend/server/v2/library/routes_test.py | 6 ++++++ .../app/(no-navbar)/onboarding/6-congrats/page.tsx | 13 ++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/autogpt_platform/backend/backend/server/v2/library/routes_test.py b/autogpt_platform/backend/backend/server/v2/library/routes_test.py index 85f66c3df207..e2cd0252d39e 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes_test.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes_test.py @@ -1,5 +1,6 @@ import datetime import json +from unittest.mock import AsyncMock import fastapi.testclient import pytest @@ -221,6 +222,10 @@ def test_add_agent_to_library_success( "backend.server.v2.library.db.add_store_agent_to_library" ) mock_db_call.return_value = mock_library_agent + mock_complete_onboarding = mocker.patch( + "backend.server.v2.library.routes.agents.complete_onboarding_step", + new_callable=AsyncMock, + ) response = client.post( "/agents", json={"store_listing_version_id": "test-version-id"} @@ -235,6 +240,7 @@ def test_add_agent_to_library_success( mock_db_call.assert_called_once_with( store_listing_version_id="test-version-id", user_id=test_user_id ) + mock_complete_onboarding.assert_awaited_once() def test_add_agent_to_library_error(mocker: pytest_mock.MockFixture, test_user_id: str): diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/page.tsx index 6fcc40a53f85..b3b4e4f4586f 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/page.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/6-congrats/page.tsx @@ -5,6 +5,9 @@ import { useRouter } from "next/navigation"; import * as party from "party-js"; import { useEffect, useRef, useState } from "react"; import { useOnboarding } from "../../../../providers/onboarding/onboarding-provider"; +import { resolveResponse } from "@/app/api/helpers"; +import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding"; +import { postV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library"; export default function Page() { const { completeStep } = useOnboarding(7, "AGENT_INPUT"); @@ -37,11 +40,15 @@ export default function Page() { completeStep("CONGRATS"); try { - const onboarding = await api.getUserOnboarding(); + const onboarding = await resolveResponse(getV1OnboardingState()); if (onboarding?.selectedStoreListingVersionId) { try { - const libraryAgent = await api.addMarketplaceAgentToLibrary( - onboarding.selectedStoreListingVersionId, + const libraryAgent = await resolveResponse( + postV2AddMarketplaceAgent({ + store_listing_version_id: + onboarding.selectedStoreListingVersionId, + source: "onboarding", + }), ); router.replace(`/library/agents/${libraryAgent.id}`); } catch (error) { From ab3d325abcb5b8fc593e2d9c0664ac8e0750b583 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 19 Nov 2025 11:50:33 +0900 Subject: [PATCH 29/33] Fix openapi schema --- .../backend/backend/server/routers/v1.py | 33 +- .../frontend/src/app/api/openapi.json | 2257 +++-------------- 2 files changed, 344 insertions(+), 1946 deletions(-) diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 1d3ad6f8bc42..ffff25c2409c 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -24,7 +24,6 @@ UploadFile, ) from fastapi.concurrency import run_in_threadpool -from prisma.models import UserOnboarding from pydantic import BaseModel from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND from typing_extensions import Optional, TypedDict @@ -119,6 +118,25 @@ def _create_file_size_error(size_bytes: int, max_size_mb: int) -> HTTPException: logger = logging.getLogger(__name__) +# Needed to avoid including User from UserOnboarding prisma model in router +# that causes schema generation for prisma and our LibraryAgent in openapi.json +class UserOnboarding(pydantic.BaseModel): + userId: str + completedSteps: list[OnboardingStep] + walletShown: bool + notified: list[OnboardingStep] + rewardedFor: list[OnboardingStep] + usageReason: Optional[str] + integrations: list[str] + otherIntegrations: Optional[str] + selectedStoreListingVersionId: Optional[str] + agentInput: dict[str, Any] + onboardingAgentExecutionId: Optional[str] + agentRuns: int + lastRunAt: Optional[datetime] + consecutiveRunDays: int + + async def hide_activity_summaries_if_disabled( executions: list[execution_db.GraphExecutionMeta], user_id: str ) -> list[execution_db.GraphExecutionMeta]: @@ -263,10 +281,9 @@ async def update_preferences( summary="Onboarding state", tags=["onboarding"], dependencies=[Security(requires_user)], + response_model=UserOnboarding, ) -async def get_onboarding( - user_id: Annotated[str, Security(get_user_id)] -) -> UserOnboarding: +async def get_onboarding(user_id: Annotated[str, Security(get_user_id)]): return await get_user_onboarding(user_id) @@ -275,10 +292,11 @@ async def get_onboarding( summary="Update onboarding state", tags=["onboarding"], dependencies=[Security(requires_user)], + response_model=UserOnboarding, ) async def update_onboarding( user_id: Annotated[str, Security(get_user_id)], data: UserOnboardingUpdate -) -> UserOnboarding: +): return await update_user_onboarding(user_id, data) @@ -323,10 +341,9 @@ async def is_onboarding_enabled() -> bool: summary="Reset onboarding progress", tags=["onboarding"], dependencies=[Security(requires_user)], + response_model=UserOnboarding, ) -async def reset_onboarding( - user_id: Annotated[str, Security(get_user_id)] -) -> UserOnboarding: +async def reset_onboarding(user_id: Annotated[str, Security(get_user_id)]): return await reset_user_onboarding(user_id) diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index 0fa680a017b2..7f59be195c6a 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -2611,7 +2611,7 @@ "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/Profile-Input" } + "schema": { "$ref": "#/components/schemas/Profile" } } }, "required": true @@ -4421,9 +4421,7 @@ "description": "Agent added successfully", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - } + "schema": { "$ref": "#/components/schemas/LibraryAgent" } } } }, @@ -4526,9 +4524,7 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - } + "schema": { "$ref": "#/components/schemas/LibraryAgent" } } } }, @@ -4574,9 +4570,7 @@ "description": "Agent updated successfully", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - } + "schema": { "$ref": "#/components/schemas/LibraryAgent" } } } }, @@ -4658,9 +4652,7 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - } + "schema": { "$ref": "#/components/schemas/LibraryAgent" } } } }, @@ -4700,9 +4692,7 @@ "application/json": { "schema": { "anyOf": [ - { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - }, + { "$ref": "#/components/schemas/LibraryAgent" }, { "type": "null" } ], "title": "Response Getv2Get Agent By Store Id" @@ -4743,9 +4733,7 @@ "description": "Successful Response", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - } + "schema": { "$ref": "#/components/schemas/LibraryAgent" } } } }, @@ -5084,69 +5072,6 @@ }, "components": { "schemas": { - "APIKey": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "name": { "type": "string", "title": "Name" }, - "head": { "type": "string", "title": "Head" }, - "tail": { "type": "string", "title": "Tail" }, - "hash": { "type": "string", "title": "Hash" }, - "salt": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Salt" - }, - "status": { "$ref": "#/components/schemas/APIKeyStatus" }, - "permissions": { - "items": { "$ref": "#/components/schemas/APIKeyPermission" }, - "type": "array", - "title": "Permissions" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "lastUsedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Lastusedat" - }, - "revokedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Revokedat" - }, - "description": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Description" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - } - }, - "type": "object", - "required": [ - "id", - "name", - "head", - "tail", - "hash", - "status", - "permissions", - "createdAt", - "userId" - ], - "title": "APIKey", - "description": "Represents a APIKey record" - }, "APIKeyCredentials": { "properties": { "id": { "type": "string", "title": "Id" }, @@ -5254,28 +5179,6 @@ "required": ["new_balance", "transaction_key"], "title": "AddUserCreditsResponse" }, - "AgentBlock": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "name": { "type": "string", "title": "Name" }, - "inputSchema": { "type": "string", "title": "Inputschema" }, - "outputSchema": { "type": "string", "title": "Outputschema" }, - "ReferencedByAgentNode": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNode" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Referencedbyagentnode" - } - }, - "type": "object", - "required": ["id", "name", "inputSchema", "outputSchema"], - "title": "AgentBlock", - "description": "Represents a AgentBlock record" - }, "AgentExecutionStatus": { "type": "string", "enum": [ @@ -5288,805 +5191,153 @@ ], "title": "AgentExecutionStatus" }, - "AgentGraph": { + "ApiResponse": { "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version" }, - "createdAt": { + "answer": { "type": "string", "title": "Answer" }, + "documents": { + "items": { "$ref": "#/components/schemas/Document" }, + "type": "array", + "title": "Documents" + }, + "success": { "type": "boolean", "title": "Success" } + }, + "type": "object", + "required": ["answer", "documents", "success"], + "title": "ApiResponse" + }, + "AutoTopUpConfig": { + "properties": { + "amount": { "type": "integer", "title": "Amount" }, + "threshold": { "type": "integer", "title": "Threshold" } + }, + "type": "object", + "required": ["amount", "threshold"], + "title": "AutoTopUpConfig" + }, + "AyrshareSSOResponse": { + "properties": { + "sso_url": { + "type": "string", + "title": "Sso Url", + "description": "The SSO URL for Ayrshare integration" + }, + "expires_at": { "type": "string", "format": "date-time", - "title": "Createdat" + "title": "Expires At", + "description": "ISO timestamp when the URL expires" + } + }, + "type": "object", + "required": ["sso_url", "expires_at"], + "title": "AyrshareSSOResponse" + }, + "BaseGraph-Input": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true }, - "updatedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Updatedat" + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" }, - "name": { + "recommended_schedule_cron": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Name" + "title": "Recommended Schedule Cron" }, - "description": { + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Description" + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + } + }, + "type": "object", + "required": ["name", "description"], + "title": "BaseGraph" + }, + "BaseGraph-Output": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, "instructions": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Instructions" }, - "recommendedScheduleCron": { + "recommended_schedule_cron": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommendedschedulecron" + "title": "Recommended Schedule Cron" }, - "isActive": { "type": "boolean", "title": "Isactive" }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] }, - "forkedFromId": { + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Forkedfromid" + "title": "Forked From Id" }, - "forkedFromVersion": { + "forked_from_version": { "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Forkedfromversion" - }, - "forkedFrom": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "forks": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraph" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Forks" - }, - "Nodes": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNode" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Nodes" - }, - "Executions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Executions" + "title": "Forked From Version" }, - "Presets": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentPreset" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Presets" + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema", + "readOnly": true }, - "LibraryAgents": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/prisma__models__LibraryAgent" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Libraryagents" + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema", + "readOnly": true }, - "StoreListings": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListing" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Storelistings" + "has_external_trigger": { + "type": "boolean", + "title": "Has External Trigger", + "readOnly": true }, - "StoreListingVersions": { + "trigger_setup_info": { "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListingVersion" }, - "type": "array" - }, + { "$ref": "#/components/schemas/GraphTriggerInfo" }, { "type": "null" } ], - "title": "Storelistingversions" - } - }, - "type": "object", - "required": ["id", "version", "createdAt", "isActive", "userId"], - "title": "AgentGraph", - "description": "Represents a AgentGraph record" - }, - "AgentGraphExecution": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Updatedat" - }, - "startedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Startedat" - }, - "isDeleted": { "type": "boolean", "title": "Isdeleted" }, - "executionStatus": { - "$ref": "#/components/schemas/AgentExecutionStatus" - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "agentPresetId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Agentpresetid" - }, - "AgentPreset": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentPreset" }, - { "type": "null" } - ] - }, - "inputs": { "anyOf": [{}, { "type": "null" }], "title": "Inputs" }, - "credentialInputs": { - "anyOf": [{}, { "type": "null" }], - "title": "Credentialinputs" - }, - "nodesInputMasks": { - "anyOf": [{}, { "type": "null" }], - "title": "Nodesinputmasks" - }, - "NodeExecutions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNodeExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Nodeexecutions" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "stats": { "anyOf": [{}, { "type": "null" }], "title": "Stats" }, - "parentGraphExecutionId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Parentgraphexecutionid" - }, - "ParentExecution": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraphExecution" }, - { "type": "null" } - ] - }, - "ChildExecutions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Childexecutions" - }, - "isShared": { "type": "boolean", "title": "Isshared" }, - "shareToken": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Sharetoken" - }, - "sharedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Sharedat" - } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "isDeleted", - "executionStatus", - "agentGraphId", - "agentGraphVersion", - "userId", - "isShared" - ], - "title": "AgentGraphExecution", - "description": "Represents a AgentGraphExecution record" - }, - "AgentNode": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "agentBlockId": { "type": "string", "title": "Agentblockid" }, - "AgentBlock": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentBlock" }, - { "type": "null" } - ] - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "Input": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNodeLink" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Input" - }, - "Output": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNodeLink" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Output" - }, - "constantInput": { "title": "Constantinput" }, - "webhookId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Webhookid" - }, - "Webhook": { - "anyOf": [ - { "$ref": "#/components/schemas/IntegrationWebhook" }, - { "type": "null" } - ] - }, - "metadata": { "title": "Metadata" }, - "Executions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNodeExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Executions" - } - }, - "type": "object", - "required": [ - "id", - "agentBlockId", - "agentGraphId", - "agentGraphVersion", - "constantInput", - "metadata" - ], - "title": "AgentNode", - "description": "Represents a AgentNode record" - }, - "AgentNodeExecution": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "agentGraphExecutionId": { - "type": "string", - "title": "Agentgraphexecutionid" - }, - "GraphExecution": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraphExecution" }, - { "type": "null" } - ] - }, - "agentNodeId": { "type": "string", "title": "Agentnodeid" }, - "Node": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentNode" }, - { "type": "null" } - ] - }, - "Input": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Input" - }, - "Output": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Output" - }, - "executionStatus": { - "$ref": "#/components/schemas/AgentExecutionStatus" - }, - "executionData": { - "anyOf": [{}, { "type": "null" }], - "title": "Executiondata" - }, - "addedTime": { - "type": "string", - "format": "date-time", - "title": "Addedtime" - }, - "queuedTime": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Queuedtime" - }, - "startedTime": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Startedtime" - }, - "endedTime": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Endedtime" - }, - "stats": { "anyOf": [{}, { "type": "null" }], "title": "Stats" } - }, - "type": "object", - "required": [ - "id", - "agentGraphExecutionId", - "agentNodeId", - "executionStatus", - "addedTime" - ], - "title": "AgentNodeExecution", - "description": "Represents a AgentNodeExecution record" - }, - "AgentNodeExecutionInputOutput": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "name": { "type": "string", "title": "Name" }, - "data": { "anyOf": [{}, { "type": "null" }], "title": "Data" }, - "time": { "type": "string", "format": "date-time", "title": "Time" }, - "referencedByInputExecId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Referencedbyinputexecid" - }, - "ReferencedByInputExec": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentNodeExecution" }, - { "type": "null" } - ] - }, - "referencedByOutputExecId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Referencedbyoutputexecid" - }, - "ReferencedByOutputExec": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentNodeExecution" }, - { "type": "null" } - ] - }, - "agentPresetId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Agentpresetid" - }, - "AgentPreset": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentPreset" }, - { "type": "null" } - ] - } - }, - "type": "object", - "required": ["id", "name", "time"], - "title": "AgentNodeExecutionInputOutput", - "description": "Represents a AgentNodeExecutionInputOutput record" - }, - "AgentNodeLink": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "agentNodeSourceId": { - "type": "string", - "title": "Agentnodesourceid" - }, - "AgentNodeSource": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentNode" }, - { "type": "null" } - ] - }, - "sourceName": { "type": "string", "title": "Sourcename" }, - "agentNodeSinkId": { "type": "string", "title": "Agentnodesinkid" }, - "AgentNodeSink": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentNode" }, - { "type": "null" } - ] - }, - "sinkName": { "type": "string", "title": "Sinkname" }, - "isStatic": { "type": "boolean", "title": "Isstatic" } - }, - "type": "object", - "required": [ - "id", - "agentNodeSourceId", - "sourceName", - "agentNodeSinkId", - "sinkName", - "isStatic" - ], - "title": "AgentNodeLink", - "description": "Represents a AgentNodeLink record" - }, - "AgentPreset": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "isActive": { "type": "boolean", "title": "Isactive" }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "InputPresets": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/AgentNodeExecutionInputOutput" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Inputpresets" - }, - "Executions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Executions" - }, - "webhookId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Webhookid" - }, - "Webhook": { - "anyOf": [ - { "$ref": "#/components/schemas/IntegrationWebhook" }, - { "type": "null" } - ] - }, - "isDeleted": { "type": "boolean", "title": "Isdeleted" } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "name", - "description", - "isActive", - "userId", - "agentGraphId", - "agentGraphVersion", - "isDeleted" - ], - "title": "AgentPreset", - "description": "Represents a AgentPreset record" - }, - "AnalyticsDetails": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "type": { "type": "string", "title": "Type" }, - "data": { "anyOf": [{}, { "type": "null" }], "title": "Data" }, - "dataIndex": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Dataindex" - } - }, - "type": "object", - "required": ["id", "createdAt", "updatedAt", "userId", "type"], - "title": "AnalyticsDetails", - "description": "Represents a AnalyticsDetails record" - }, - "AnalyticsMetrics": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "analyticMetric": { "type": "string", "title": "Analyticmetric" }, - "value": { "type": "number", "title": "Value" }, - "dataString": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Datastring" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "analyticMetric", - "value", - "userId" - ], - "title": "AnalyticsMetrics", - "description": "/////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////\n/////////// METRICS TRACKING TABLES ////////////////\n/////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////" - }, - "ApiResponse": { - "properties": { - "answer": { "type": "string", "title": "Answer" }, - "documents": { - "items": { "$ref": "#/components/schemas/Document" }, - "type": "array", - "title": "Documents" - }, - "success": { "type": "boolean", "title": "Success" } - }, - "type": "object", - "required": ["answer", "documents", "success"], - "title": "ApiResponse" - }, - "AutoTopUpConfig": { - "properties": { - "amount": { "type": "integer", "title": "Amount" }, - "threshold": { "type": "integer", "title": "Threshold" } - }, - "type": "object", - "required": ["amount", "threshold"], - "title": "AutoTopUpConfig" - }, - "AyrshareSSOResponse": { - "properties": { - "sso_url": { - "type": "string", - "title": "Sso Url", - "description": "The SSO URL for Ayrshare integration" - }, - "expires_at": { - "type": "string", - "format": "date-time", - "title": "Expires At", - "description": "ISO timestamp when the URL expires" - } - }, - "type": "object", - "required": ["sso_url", "expires_at"], - "title": "AyrshareSSOResponse" - }, - "BaseGraph-Input": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version", "default": 1 }, - "is_active": { - "type": "boolean", - "title": "Is Active", - "default": true - }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" - }, - "recommended_schedule_cron": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" - }, - "nodes": { - "items": { "$ref": "#/components/schemas/Node" }, - "type": "array", - "title": "Nodes", - "default": [] - }, - "links": { - "items": { "$ref": "#/components/schemas/Link" }, - "type": "array", - "title": "Links", - "default": [] - }, - "forked_from_id": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Forked From Id" - }, - "forked_from_version": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Forked From Version" - } - }, - "type": "object", - "required": ["name", "description"], - "title": "BaseGraph" - }, - "BaseGraph-Output": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version", "default": 1 }, - "is_active": { - "type": "boolean", - "title": "Is Active", - "default": true - }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" - }, - "recommended_schedule_cron": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" - }, - "nodes": { - "items": { "$ref": "#/components/schemas/Node" }, - "type": "array", - "title": "Nodes", - "default": [] - }, - "links": { - "items": { "$ref": "#/components/schemas/Link" }, - "type": "array", - "title": "Links", - "default": [] - }, - "forked_from_id": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Forked From Id" - }, - "forked_from_version": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Forked From Version" - }, - "input_schema": { - "additionalProperties": true, - "type": "object", - "title": "Input Schema", - "readOnly": true - }, - "output_schema": { - "additionalProperties": true, - "type": "object", - "title": "Output Schema", - "readOnly": true - }, - "has_external_trigger": { - "type": "boolean", - "title": "Has External Trigger", - "readOnly": true - }, - "trigger_setup_info": { - "anyOf": [ - { "$ref": "#/components/schemas/GraphTriggerInfo" }, - { "type": "null" } - ], - "readOnly": true + "readOnly": true } }, "type": "object", @@ -6585,42 +5836,6 @@ "required": ["id", "provider", "type", "title", "scopes", "username"], "title": "CredentialsMetaResponse" }, - "CreditTransaction": { - "properties": { - "transactionKey": { "type": "string", "title": "Transactionkey" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "amount": { "type": "integer", "title": "Amount" }, - "type": { "$ref": "#/components/schemas/CreditTransactionType" }, - "runningBalance": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Runningbalance" - }, - "isActive": { "type": "boolean", "title": "Isactive" }, - "metadata": { "anyOf": [{}, { "type": "null" }], "title": "Metadata" } - }, - "type": "object", - "required": [ - "transactionKey", - "createdAt", - "userId", - "amount", - "type", - "isActive" - ], - "title": "CreditTransaction", - "description": "Represents a CreditTransaction record" - }, "CreditTransactionType": { "type": "string", "enum": ["TOP_UP", "USAGE", "GRANT", "REFUND", "CARD_CHECK"], @@ -7416,80 +6631,99 @@ "required": ["provider", "host"], "title": "HostScopedCredentials" }, - "IntegrationWebhook": { + "LibraryAgent": { "properties": { "id": { "type": "string", "title": "Id" }, - "createdAt": { + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "image_url": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Image Url" + }, + "creator_name": { "type": "string", "title": "Creator Name" }, + "creator_image_url": { + "type": "string", + "title": "Creator Image Url" + }, + "status": { "$ref": "#/components/schemas/LibraryAgentStatus" }, + "updated_at": { "type": "string", "format": "date-time", - "title": "Createdat" + "title": "Updated At" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "instructions": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Instructions" + }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema" + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema" }, - "updatedAt": { + "credentials_input_schema": { "anyOf": [ - { "type": "string", "format": "date-time" }, + { "additionalProperties": true, "type": "object" }, { "type": "null" } ], - "title": "Updatedat" + "title": "Credentials Input Schema", + "description": "Input schema for credentials required by the agent" }, - "userId": { "type": "string", "title": "Userid" }, - "User": { + "has_external_trigger": { + "type": "boolean", + "title": "Has External Trigger", + "description": "Whether the agent has an external trigger (e.g. webhook) node" + }, + "trigger_setup_info": { "anyOf": [ - { "$ref": "#/components/schemas/User" }, + { "$ref": "#/components/schemas/GraphTriggerInfo" }, { "type": "null" } ] }, - "provider": { "type": "string", "title": "Provider" }, - "credentialsId": { "type": "string", "title": "Credentialsid" }, - "webhookType": { "type": "string", "title": "Webhooktype" }, - "resource": { "type": "string", "title": "Resource" }, - "events": { - "items": { "type": "string" }, - "type": "array", - "title": "Events" - }, - "config": { "title": "Config" }, - "secret": { "type": "string", "title": "Secret" }, - "providerWebhookId": { - "type": "string", - "title": "Providerwebhookid" + "new_output": { "type": "boolean", "title": "New Output" }, + "can_access_graph": { + "type": "boolean", + "title": "Can Access Graph" }, - "AgentNodes": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentNode" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Agentnodes" + "is_latest_version": { + "type": "boolean", + "title": "Is Latest Version" }, - "AgentPresets": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentPreset" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Agentpresets" + "is_favorite": { "type": "boolean", "title": "Is Favorite" }, + "recommended_schedule_cron": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Recommended Schedule Cron" } }, "type": "object", "required": [ "id", - "createdAt", - "userId", - "provider", - "credentialsId", - "webhookType", - "resource", - "events", - "config", - "secret", - "providerWebhookId" + "graph_id", + "graph_version", + "image_url", + "creator_name", + "creator_image_url", + "status", + "updated_at", + "name", + "description", + "input_schema", + "output_schema", + "credentials_input_schema", + "has_external_trigger", + "new_output", + "can_access_graph", + "is_latest_version", + "is_favorite" ], - "title": "IntegrationWebhook", - "description": "Represents a IntegrationWebhook record" + "title": "LibraryAgent", + "description": "Represents an agent in the library, including metadata for display and\nuser interaction within the system." }, "LibraryAgentPreset": { "properties": { @@ -7668,9 +6902,7 @@ "LibraryAgentResponse": { "properties": { "agents": { - "items": { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - }, + "items": { "$ref": "#/components/schemas/LibraryAgent" }, "type": "array", "title": "Agents" }, @@ -7954,37 +7186,6 @@ "required": ["block_id", "graph_id", "graph_version"], "title": "NodeModel" }, - "NotificationEvent": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "UserNotificationBatch": { - "anyOf": [ - { "$ref": "#/components/schemas/UserNotificationBatch" }, - { "type": "null" } - ] - }, - "userNotificationBatchId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Usernotificationbatchid" - }, - "type": { "$ref": "#/components/schemas/NotificationType" }, - "data": { "title": "Data" } - }, - "type": "object", - "required": ["id", "createdAt", "updatedAt", "type", "data"], - "title": "NotificationEvent", - "description": "Represents a NotificationEvent record" - }, "NotificationPreference": { "properties": { "user_id": { "type": "string", "title": "User Id" }, @@ -8482,7 +7683,7 @@ ], "title": "PostmarkSubscriptionChangeWebhook" }, - "Profile-Input": { + "Profile": { "properties": { "name": { "type": "string", "title": "Name" }, "username": { "type": "string", "title": "Username" }, @@ -8503,69 +7704,6 @@ "required": ["name", "username", "description", "links", "avatar_url"], "title": "Profile" }, - "Profile-Output": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "userId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Userid" - }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "name": { "type": "string", "title": "Name" }, - "username": { "type": "string", "title": "Username" }, - "description": { "type": "string", "title": "Description" }, - "links": { - "items": { "type": "string" }, - "type": "array", - "title": "Links" - }, - "avatarUrl": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Avatarurl" - }, - "isFeatured": { "type": "boolean", "title": "Isfeatured" }, - "LibraryAgents": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/prisma__models__LibraryAgent" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Libraryagents" - } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "name", - "username", - "description", - "links", - "isFeatured" - ], - "title": "Profile", - "description": "Represents a Profile record" - }, "ProfileDetails": { "properties": { "name": { "type": "string", "title": "Name" }, @@ -8760,9 +7898,7 @@ "items": { "anyOf": [ { "$ref": "#/components/schemas/BlockInfo" }, - { - "$ref": "#/components/schemas/backend__server__v2__library__model__LibraryAgent" - }, + { "$ref": "#/components/schemas/LibraryAgent" }, { "$ref": "#/components/schemas/StoreAgent" } ] }, @@ -9003,309 +8139,45 @@ }, "active_version_id": { "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Active Version Id" - }, - "has_approved_version": { - "type": "boolean", - "title": "Has Approved Version", - "default": false - } - }, - "type": "object", - "required": [ - "store_listing_version_id", - "slug", - "agent_name", - "agent_video", - "agent_image", - "creator", - "creator_avatar", - "sub_heading", - "description", - "categories", - "runs", - "rating", - "versions", - "last_updated" - ], - "title": "StoreAgentDetails" - }, - "StoreAgentsResponse": { - "properties": { - "agents": { - "items": { "$ref": "#/components/schemas/StoreAgent" }, - "type": "array", - "title": "Agents" - }, - "pagination": { "$ref": "#/components/schemas/Pagination" } - }, - "type": "object", - "required": ["agents", "pagination"], - "title": "StoreAgentsResponse" - }, - "StoreListing": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "isDeleted": { "type": "boolean", "title": "Isdeleted" }, - "hasApprovedVersion": { - "type": "boolean", - "title": "Hasapprovedversion" - }, - "slug": { "type": "string", "title": "Slug" }, - "useForOnboarding": { - "type": "boolean", - "title": "Useforonboarding" - }, - "activeVersionId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Activeversionid" - }, - "ActiveVersion": { - "anyOf": [ - { "$ref": "#/components/schemas/StoreListingVersion" }, - { "type": "null" } - ] - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "owningUserId": { "type": "string", "title": "Owninguserid" }, - "OwningUser": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "Versions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListingVersion" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Versions" - } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "isDeleted", - "hasApprovedVersion", - "slug", - "useForOnboarding", - "agentGraphId", - "agentGraphVersion", - "owningUserId" - ], - "title": "StoreListing", - "description": "Represents a StoreListing record" - }, - "StoreListingReview": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "storeListingVersionId": { - "type": "string", - "title": "Storelistingversionid" - }, - "StoreListingVersion": { - "anyOf": [ - { "$ref": "#/components/schemas/StoreListingVersion" }, - { "type": "null" } - ] - }, - "reviewByUserId": { "type": "string", "title": "Reviewbyuserid" }, - "ReviewByUser": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "score": { "type": "integer", "title": "Score" }, - "comments": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Comments" - } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "storeListingVersionId", - "reviewByUserId", - "score" - ], - "title": "StoreListingReview", - "description": "Represents a StoreListingReview record" - }, - "StoreListingVersion": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "version": { "type": "integer", "title": "Version" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "name": { "type": "string", "title": "Name" }, - "subHeading": { "type": "string", "title": "Subheading" }, - "videoUrl": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Videourl" - }, - "imageUrls": { - "items": { "type": "string" }, - "type": "array", - "title": "Imageurls" - }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" - }, - "categories": { - "items": { "type": "string" }, - "type": "array", - "title": "Categories" - }, - "isFeatured": { "type": "boolean", "title": "Isfeatured" }, - "isDeleted": { "type": "boolean", "title": "Isdeleted" }, - "isAvailable": { "type": "boolean", "title": "Isavailable" }, - "submissionStatus": { - "$ref": "#/components/schemas/SubmissionStatus" - }, - "submittedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Submittedat" - }, - "storeListingId": { "type": "string", "title": "Storelistingid" }, - "StoreListing": { - "anyOf": [ - { "$ref": "#/components/schemas/StoreListing" }, - { "type": "null" } - ] - }, - "ActiveFor": { - "anyOf": [ - { "$ref": "#/components/schemas/StoreListing" }, - { "type": "null" } - ] - }, - "changesSummary": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Changessummary" - }, - "reviewerId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Reviewerid" - }, - "Reviewer": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "internalComments": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Internalcomments" - }, - "reviewComments": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Reviewcomments" - }, - "reviewedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Reviewedat" - }, - "recommendedScheduleCron": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommendedschedulecron" - }, - "Reviews": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListingReview" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Reviews" + "title": "Active Version Id" + }, + "has_approved_version": { + "type": "boolean", + "title": "Has Approved Version", + "default": false } }, "type": "object", "required": [ - "id", - "version", - "createdAt", - "updatedAt", - "agentGraphId", - "agentGraphVersion", - "name", - "subHeading", - "imageUrls", + "store_listing_version_id", + "slug", + "agent_name", + "agent_video", + "agent_image", + "creator", + "creator_avatar", + "sub_heading", "description", "categories", - "isFeatured", - "isDeleted", - "isAvailable", - "submissionStatus", - "storeListingId" + "runs", + "rating", + "versions", + "last_updated" ], - "title": "StoreListingVersion", - "description": "Represents a StoreListingVersion record" + "title": "StoreAgentDetails" + }, + "StoreAgentsResponse": { + "properties": { + "agents": { + "items": { "$ref": "#/components/schemas/StoreAgent" }, + "type": "array", + "title": "Agents" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["agents", "pagination"], + "title": "StoreAgentsResponse" }, "StoreListingWithVersions": { "properties": { @@ -10839,360 +9711,86 @@ "Pacific/Galapagos", "Pacific/Gambier", "Pacific/Guadalcanal", - "Pacific/Guam", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Kanton", - "Pacific/Kiritimati", - "Pacific/Kosrae", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Marquesas", - "Pacific/Midway", - "Pacific/Nauru", - "Pacific/Niue", - "Pacific/Norfolk", - "Pacific/Noumea", - "Pacific/Pago_Pago", - "Pacific/Palau", - "Pacific/Pitcairn", - "Pacific/Pohnpei", - "Pacific/Ponape", - "Pacific/Port_Moresby", - "Pacific/Rarotonga", - "Pacific/Saipan", - "Pacific/Samoa", - "Pacific/Tahiti", - "Pacific/Tarawa", - "Pacific/Tongatapu", - "Pacific/Truk", - "Pacific/Wake", - "Pacific/Wallis", - "Pacific/Yap", - "Poland", - "Portugal", - "ROC", - "ROK", - "Singapore", - "Turkey", - "UCT", - "US/Alaska", - "US/Aleutian", - "US/Arizona", - "US/Central", - "US/East-Indiana", - "US/Eastern", - "US/Hawaii", - "US/Indiana-Starke", - "US/Michigan", - "US/Mountain", - "US/Pacific", - "US/Samoa", - "UTC", - "Universal", - "W-SU", - "WET", - "Zulu" - ], - "minLength": 1, - "title": "Timezone" - } - }, - "type": "object", - "required": ["timezone"], - "title": "UpdateTimezoneRequest" - }, - "UploadFileResponse": { - "properties": { - "file_uri": { "type": "string", "title": "File Uri" }, - "file_name": { "type": "string", "title": "File Name" }, - "size": { "type": "integer", "title": "Size" }, - "content_type": { "type": "string", "title": "Content Type" }, - "expires_in_hours": { "type": "integer", "title": "Expires In Hours" } - }, - "type": "object", - "required": [ - "file_uri", - "file_name", - "size", - "content_type", - "expires_in_hours" - ], - "title": "UploadFileResponse" - }, - "User": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "email": { "type": "string", "title": "Email" }, - "emailVerified": { "type": "boolean", "title": "Emailverified" }, - "name": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Name" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "metadata": { "title": "Metadata" }, - "integrations": { "type": "string", "title": "Integrations" }, - "stripeCustomerId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Stripecustomerid" - }, - "topUpConfig": { - "anyOf": [{}, { "type": "null" }], - "title": "Topupconfig" - }, - "maxEmailsPerDay": { "type": "integer", "title": "Maxemailsperday" }, - "notifyOnAgentRun": { - "type": "boolean", - "title": "Notifyonagentrun" - }, - "notifyOnZeroBalance": { - "type": "boolean", - "title": "Notifyonzerobalance" - }, - "notifyOnLowBalance": { - "type": "boolean", - "title": "Notifyonlowbalance" - }, - "notifyOnBlockExecutionFailed": { - "type": "boolean", - "title": "Notifyonblockexecutionfailed" - }, - "notifyOnContinuousAgentError": { - "type": "boolean", - "title": "Notifyoncontinuousagenterror" - }, - "notifyOnDailySummary": { - "type": "boolean", - "title": "Notifyondailysummary" - }, - "notifyOnWeeklySummary": { - "type": "boolean", - "title": "Notifyonweeklysummary" - }, - "notifyOnMonthlySummary": { - "type": "boolean", - "title": "Notifyonmonthlysummary" - }, - "notifyOnAgentApproved": { - "type": "boolean", - "title": "Notifyonagentapproved" - }, - "notifyOnAgentRejected": { - "type": "boolean", - "title": "Notifyonagentrejected" - }, - "timezone": { "type": "string", "title": "Timezone" }, - "AgentGraphs": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraph" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Agentgraphs" - }, - "AgentGraphExecutions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentGraphExecution" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Agentgraphexecutions" - }, - "AnalyticsDetails": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AnalyticsDetails" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Analyticsdetails" - }, - "AnalyticsMetrics": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AnalyticsMetrics" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Analyticsmetrics" - }, - "CreditTransactions": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/CreditTransaction" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Credittransactions" - }, - "UserBalance": { - "anyOf": [ - { "$ref": "#/components/schemas/UserBalance" }, - { "type": "null" } - ] - }, - "AgentPresets": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/AgentPreset" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Agentpresets" - }, - "LibraryAgents": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/prisma__models__LibraryAgent" - }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Libraryagents" - }, - "Profile": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/Profile-Output" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Profile" - }, - "UserOnboarding": { - "anyOf": [ - { "$ref": "#/components/schemas/UserOnboarding" }, - { "type": "null" } - ] - }, - "StoreListings": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListing" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Storelistings" - }, - "StoreListingReviews": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListingReview" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Storelistingreviews" - }, - "StoreVersionsReviewed": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/StoreListingVersion" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Storeversionsreviewed" - }, - "APIKeys": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/APIKey" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Apikeys" - }, - "IntegrationWebhooks": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/IntegrationWebhook" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Integrationwebhooks" - }, - "NotificationBatches": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/UserNotificationBatch" - }, - "type": "array" - }, - { "type": "null" } + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kanton", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu" ], - "title": "Notificationbatches" + "minLength": 1, + "title": "Timezone" } }, "type": "object", - "required": [ - "id", - "email", - "emailVerified", - "createdAt", - "updatedAt", - "metadata", - "integrations", - "maxEmailsPerDay", - "notifyOnAgentRun", - "notifyOnZeroBalance", - "notifyOnLowBalance", - "notifyOnBlockExecutionFailed", - "notifyOnContinuousAgentError", - "notifyOnDailySummary", - "notifyOnWeeklySummary", - "notifyOnMonthlySummary", - "notifyOnAgentApproved", - "notifyOnAgentRejected", - "timezone" - ], - "title": "User", - "description": "Represents a User record" + "required": ["timezone"], + "title": "UpdateTimezoneRequest" }, - "UserBalance": { + "UploadFileResponse": { "properties": { - "userId": { "type": "string", "title": "Userid" }, - "balance": { "type": "integer", "title": "Balance" }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "user": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - } + "file_uri": { "type": "string", "title": "File Uri" }, + "file_name": { "type": "string", "title": "File Name" }, + "size": { "type": "integer", "title": "Size" }, + "content_type": { "type": "string", "title": "Content Type" }, + "expires_in_hours": { "type": "integer", "title": "Expires In Hours" } }, "type": "object", - "required": ["userId", "balance", "updatedAt"], - "title": "UserBalance", - "description": "Represents a UserBalance record" + "required": [ + "file_uri", + "file_name", + "size", + "content_type", + "expires_in_hours" + ], + "title": "UploadFileResponse" }, "UserHistoryResponse": { "properties": { @@ -11208,58 +9806,9 @@ "title": "UserHistoryResponse", "description": "Response model for listings with version history" }, - "UserNotificationBatch": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "type": { "$ref": "#/components/schemas/NotificationType" }, - "Notifications": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/NotificationEvent" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Notifications" - } - }, - "type": "object", - "required": ["id", "createdAt", "updatedAt", "userId", "type"], - "title": "UserNotificationBatch", - "description": "Represents a UserNotificationBatch record" - }, "UserOnboarding": { "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "anyOf": [ - { "type": "string", "format": "date-time" }, - { "type": "null" } - ], - "title": "Updatedat" - }, + "userId": { "type": "string", "title": "Userid" }, "completedSteps": { "items": { "$ref": "#/components/schemas/OnboardingStep" }, "type": "array", @@ -11294,7 +9843,8 @@ "title": "Selectedstorelistingversionid" }, "agentInput": { - "anyOf": [{}, { "type": "null" }], + "additionalProperties": true, + "type": "object", "title": "Agentinput" }, "onboardingAgentExecutionId": { @@ -11312,30 +9862,26 @@ "consecutiveRunDays": { "type": "integer", "title": "Consecutiverundays" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] } }, "type": "object", "required": [ - "id", - "createdAt", + "userId", "completedSteps", "walletShown", "notified", "rewardedFor", + "usageReason", "integrations", + "otherIntegrations", + "selectedStoreListingVersionId", + "agentInput", + "onboardingAgentExecutionId", "agentRuns", - "consecutiveRunDays", - "userId" + "lastRunAt", + "consecutiveRunDays" ], - "title": "UserOnboarding", - "description": "Represents a UserOnboarding record" + "title": "UserOnboarding" }, "UserOnboardingUpdate": { "properties": { @@ -11547,171 +10093,6 @@ "url" ], "title": "Webhook" - }, - "backend__server__v2__library__model__LibraryAgent": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "graph_id": { "type": "string", "title": "Graph Id" }, - "graph_version": { "type": "integer", "title": "Graph Version" }, - "image_url": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Image Url" - }, - "creator_name": { "type": "string", "title": "Creator Name" }, - "creator_image_url": { - "type": "string", - "title": "Creator Image Url" - }, - "status": { "$ref": "#/components/schemas/LibraryAgentStatus" }, - "updated_at": { - "type": "string", - "format": "date-time", - "title": "Updated At" - }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "instructions": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Instructions" - }, - "input_schema": { - "additionalProperties": true, - "type": "object", - "title": "Input Schema" - }, - "output_schema": { - "additionalProperties": true, - "type": "object", - "title": "Output Schema" - }, - "credentials_input_schema": { - "anyOf": [ - { "additionalProperties": true, "type": "object" }, - { "type": "null" } - ], - "title": "Credentials Input Schema", - "description": "Input schema for credentials required by the agent" - }, - "has_external_trigger": { - "type": "boolean", - "title": "Has External Trigger", - "description": "Whether the agent has an external trigger (e.g. webhook) node" - }, - "trigger_setup_info": { - "anyOf": [ - { "$ref": "#/components/schemas/GraphTriggerInfo" }, - { "type": "null" } - ] - }, - "new_output": { "type": "boolean", "title": "New Output" }, - "can_access_graph": { - "type": "boolean", - "title": "Can Access Graph" - }, - "is_latest_version": { - "type": "boolean", - "title": "Is Latest Version" - }, - "is_favorite": { "type": "boolean", "title": "Is Favorite" }, - "recommended_schedule_cron": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Recommended Schedule Cron" - } - }, - "type": "object", - "required": [ - "id", - "graph_id", - "graph_version", - "image_url", - "creator_name", - "creator_image_url", - "status", - "updated_at", - "name", - "description", - "input_schema", - "output_schema", - "credentials_input_schema", - "has_external_trigger", - "new_output", - "can_access_graph", - "is_latest_version", - "is_favorite" - ], - "title": "LibraryAgent", - "description": "Represents an agent in the library, including metadata for display and\nuser interaction within the system." - }, - "prisma__models__LibraryAgent": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "createdAt": { - "type": "string", - "format": "date-time", - "title": "Createdat" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "title": "Updatedat" - }, - "userId": { "type": "string", "title": "Userid" }, - "User": { - "anyOf": [ - { "$ref": "#/components/schemas/User" }, - { "type": "null" } - ] - }, - "imageUrl": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Imageurl" - }, - "agentGraphId": { "type": "string", "title": "Agentgraphid" }, - "agentGraphVersion": { - "type": "integer", - "title": "Agentgraphversion" - }, - "AgentGraph": { - "anyOf": [ - { "$ref": "#/components/schemas/AgentGraph" }, - { "type": "null" } - ] - }, - "creatorId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Creatorid" - }, - "Creator": { - "anyOf": [ - { "$ref": "#/components/schemas/Profile-Output" }, - { "type": "null" } - ] - }, - "useGraphIsActiveVersion": { - "type": "boolean", - "title": "Usegraphisactiveversion" - }, - "isFavorite": { "type": "boolean", "title": "Isfavorite" }, - "isCreatedByUser": { "type": "boolean", "title": "Iscreatedbyuser" }, - "isArchived": { "type": "boolean", "title": "Isarchived" }, - "isDeleted": { "type": "boolean", "title": "Isdeleted" } - }, - "type": "object", - "required": [ - "id", - "createdAt", - "updatedAt", - "userId", - "agentGraphId", - "agentGraphVersion", - "useGraphIsActiveVersion", - "isFavorite", - "isCreatedByUser", - "isArchived", - "isDeleted" - ], - "title": "LibraryAgent", - "description": "Represents a LibraryAgent record" } }, "securitySchemes": { From 49e8afc1b16cd546a61905884a70e0f82a2e578e Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 20 Nov 2025 11:09:31 +0900 Subject: [PATCH 30/33] Remove duplicated call --- autogpt_platform/backend/backend/data/onboarding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index c672a626a1fe..62e5b167421a 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -175,7 +175,6 @@ async def complete_onboarding_step(user_id: str, step: OnboardingStep): """ onboarding = await get_user_onboarding(user_id) if step not in onboarding.completedSteps: - onboarding = await get_user_onboarding(user_id) await UserOnboarding.prisma().update( where={"userId": user_id}, data={ From db1abb1dfe68d8746e5e93cf093901d8fdf5a0be Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 20 Nov 2025 11:10:54 +0900 Subject: [PATCH 31/33] Format --- .../frontend/src/app/(platform)/build/hooks/useSaveGraph.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts b/autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts index 722ee60308ae..d0409fd92851 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts @@ -151,7 +151,9 @@ export const useSaveGraph = ({ links: graphLinks, }; - const response = await createNewGraph({ data: { graph: data, source: "builder" } }); + const response = await createNewGraph({ + data: { graph: data, source: "builder" }, + }); const graphData = response.data as GraphModel; setGraphSchemas( graphData.input_schema, From 63384a817ee6d0d13a5c97090eb3b7a627662459 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Thu, 20 Nov 2025 11:27:15 +0900 Subject: [PATCH 32/33] Move `UserOnboarding` to `model.py` --- .../backend/backend/data/model.py | 19 ++++++++++++++++- .../backend/backend/server/routers/v1.py | 21 +------------------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/autogpt_platform/backend/backend/data/model.py b/autogpt_platform/backend/backend/data/model.py index 1c32a9f4444b..da89dcd2fecc 100644 --- a/autogpt_platform/backend/backend/data/model.py +++ b/autogpt_platform/backend/backend/data/model.py @@ -22,7 +22,7 @@ from urllib.parse import urlparse from uuid import uuid4 -from prisma.enums import CreditTransactionType +from prisma.enums import CreditTransactionType, OnboardingStep from pydantic import ( BaseModel, ConfigDict, @@ -855,3 +855,20 @@ class UserExecutionSummaryStats(BaseModel): total_execution_time: float = Field(default=0) average_execution_time: float = Field(default=0) cost_breakdown: dict[str, float] = Field(default_factory=dict) + + +class UserOnboarding(BaseModel): + userId: str + completedSteps: list[OnboardingStep] + walletShown: bool + notified: list[OnboardingStep] + rewardedFor: list[OnboardingStep] + usageReason: Optional[str] + integrations: list[str] + otherIntegrations: Optional[str] + selectedStoreListingVersionId: Optional[str] + agentInput: dict[str, Any] + onboardingAgentExecutionId: Optional[str] + agentRuns: int + lastRunAt: Optional[datetime] + consecutiveRunDays: int diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index 5743bdb74d07..9d5a12b8a625 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -45,7 +45,7 @@ set_auto_top_up, ) from backend.data.execution import UserContext -from backend.data.model import CredentialsMetaInput +from backend.data.model import CredentialsMetaInput, UserOnboarding from backend.data.notifications import NotificationPreference, NotificationPreferenceDTO from backend.data.onboarding import ( FrontendOnboardingStep, @@ -118,25 +118,6 @@ def _create_file_size_error(size_bytes: int, max_size_mb: int) -> HTTPException: logger = logging.getLogger(__name__) -# Needed to avoid including User from UserOnboarding prisma model in router -# that causes schema generation for prisma and our LibraryAgent in openapi.json -class UserOnboarding(pydantic.BaseModel): - userId: str - completedSteps: list[OnboardingStep] - walletShown: bool - notified: list[OnboardingStep] - rewardedFor: list[OnboardingStep] - usageReason: Optional[str] - integrations: list[str] - otherIntegrations: Optional[str] - selectedStoreListingVersionId: Optional[str] - agentInput: dict[str, Any] - onboardingAgentExecutionId: Optional[str] - agentRuns: int - lastRunAt: Optional[datetime] - consecutiveRunDays: int - - async def hide_activity_summaries_if_disabled( executions: list[execution_db.GraphExecutionMeta], user_id: str ) -> list[execution_db.GraphExecutionMeta]: From 32958fa52fd3e50f1a60306b3311e06bc95859b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski Date: Wed, 26 Nov 2025 11:16:56 +0900 Subject: [PATCH 33/33] Refetch onboarding on agent run --- autogpt_platform/backend/backend/data/onboarding.py | 7 +++++-- autogpt_platform/backend/backend/server/model.py | 2 +- .../frontend/src/lib/autogpt-server-api/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 62e5b167421a..63c1295b6daf 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -186,7 +186,7 @@ async def complete_onboarding_step(user_id: str, step: OnboardingStep): async def _send_onboarding_notification( - user_id: str, step: OnboardingStep, event: str = "step_completed" + user_id: str, step: OnboardingStep | None, event: str = "step_completed" ): """ Sends an onboarding notification to the user. @@ -364,7 +364,7 @@ async def increment_runs(user_id: str): await UserOnboarding.prisma().update( where={"userId": user_id}, data={ - "agentRuns": new_run_count, + "agentRuns": {"increment": 1}, "lastRunAt": last_run_at, "consecutiveRunDays": consecutive_run_days, }, @@ -375,6 +375,9 @@ async def increment_runs(user_id: str): for step in new_steps: await complete_onboarding_step(user_id, step) + # Send progress notification if no steps were completed, so client refetches onboarding state + if not new_steps: + await _send_onboarding_notification(user_id, None, event="increment_runs") async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: diff --git a/autogpt_platform/backend/backend/server/model.py b/autogpt_platform/backend/backend/server/model.py index b5fbee23cf95..1d7b79cd7c8b 100644 --- a/autogpt_platform/backend/backend/server/model.py +++ b/autogpt_platform/backend/backend/server/model.py @@ -93,4 +93,4 @@ class NotificationPayload(pydantic.BaseModel): class OnboardingNotificationPayload(NotificationPayload): - step: OnboardingStep + step: OnboardingStep | None diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index 326625ee121c..3a3a219e8545 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -956,8 +956,8 @@ export interface UserOnboarding { export interface OnboardingNotificationPayload { type: "onboarding"; - event: "step_completed"; - step: OnboardingStep; + event: "step_completed" | "increment_runs"; + step: OnboardingStep | null; } export type WebSocketNotification =