apteva 0.4.56 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +216 -54
  2. package/cli.js +35 -0
  3. package/install.js +92 -0
  4. package/package.json +12 -79
  5. package/LICENSE +0 -63
  6. package/bin/apteva.js +0 -196
  7. package/dist/ActivityPage.kxzzb4yc.js +0 -3
  8. package/dist/ApiDocsPage.zq998hbm.js +0 -4
  9. package/dist/App.55rea8mn.js +0 -61
  10. package/dist/App.5ywb23z4.js +0 -53
  11. package/dist/App.6thds120.js +0 -4
  12. package/dist/App.9tctxzqm.js +0 -8
  13. package/dist/App.a8r8ttaz.js +0 -4
  14. package/dist/App.agsv5bje.js +0 -4
  15. package/dist/App.cepapqmx.js +0 -4
  16. package/dist/App.dp041gb3.js +0 -221
  17. package/dist/App.fds72zb5.js +0 -4
  18. package/dist/App.fg9qj2dq.js +0 -4
  19. package/dist/App.ndfejbm9.js +0 -4
  20. package/dist/App.nxmfmq1h.js +0 -13
  21. package/dist/App.qdfyt8ba.js +0 -4
  22. package/dist/App.x2d0ygt6.js +0 -4
  23. package/dist/App.yt9p4nr3.js +0 -20
  24. package/dist/App.zn4mw16t.js +0 -1
  25. package/dist/ConnectionsPage.8r96ryw7.js +0 -3
  26. package/dist/McpPage.3cwh0gnd.js +0 -3
  27. package/dist/SettingsPage.ykgdh5ev.js +0 -3
  28. package/dist/SkillsPage.4np1s65b.js +0 -3
  29. package/dist/TasksPage.4g08t7p6.js +0 -3
  30. package/dist/TelemetryPage.72w9pwcp.js +0 -3
  31. package/dist/TestsPage.z4fk3r7r.js +0 -3
  32. package/dist/ThreadsPage.63tcajeh.js +0 -3
  33. package/dist/apteva-kit.css +0 -1
  34. package/dist/icon.png +0 -0
  35. package/dist/index.html +0 -16
  36. package/dist/styles.css +0 -1
  37. package/scripts/postinstall.mjs +0 -102
  38. package/src/auth/index.ts +0 -394
  39. package/src/auth/middleware.ts +0 -213
  40. package/src/binary.ts +0 -536
  41. package/src/channels/index.ts +0 -40
  42. package/src/channels/telegram.ts +0 -311
  43. package/src/crypto.ts +0 -301
  44. package/src/db-tests.ts +0 -174
  45. package/src/db.ts +0 -3133
  46. package/src/integrations/agentdojo.ts +0 -559
  47. package/src/integrations/composio.ts +0 -437
  48. package/src/integrations/index.ts +0 -87
  49. package/src/integrations/skillsmp.ts +0 -318
  50. package/src/mcp-client.ts +0 -605
  51. package/src/mcp-handler.ts +0 -394
  52. package/src/mcp-platform.ts +0 -2370
  53. package/src/openapi.ts +0 -2410
  54. package/src/providers.ts +0 -597
  55. package/src/routes/api/agent-utils.ts +0 -890
  56. package/src/routes/api/agents.ts +0 -916
  57. package/src/routes/api/api-keys.ts +0 -95
  58. package/src/routes/api/channels.ts +0 -182
  59. package/src/routes/api/helpers.ts +0 -12
  60. package/src/routes/api/integrations.ts +0 -639
  61. package/src/routes/api/mcp.ts +0 -574
  62. package/src/routes/api/meta-agent.ts +0 -195
  63. package/src/routes/api/projects.ts +0 -112
  64. package/src/routes/api/providers.ts +0 -424
  65. package/src/routes/api/skills.ts +0 -537
  66. package/src/routes/api/system.ts +0 -333
  67. package/src/routes/api/telemetry.ts +0 -203
  68. package/src/routes/api/tests.ts +0 -148
  69. package/src/routes/api/triggers.ts +0 -518
  70. package/src/routes/api/users.ts +0 -148
  71. package/src/routes/api/webhooks.ts +0 -171
  72. package/src/routes/api.ts +0 -53
  73. package/src/routes/auth.ts +0 -251
  74. package/src/routes/share.ts +0 -86
  75. package/src/routes/static.ts +0 -131
  76. package/src/server.ts +0 -642
  77. package/src/test-runner.ts +0 -598
  78. package/src/triggers/agentdojo.ts +0 -253
  79. package/src/triggers/composio.ts +0 -264
  80. package/src/triggers/index.ts +0 -71
  81. package/src/tui/AgentList.tsx +0 -145
  82. package/src/tui/App.tsx +0 -102
  83. package/src/tui/Login.tsx +0 -104
  84. package/src/tui/api.ts +0 -72
  85. package/src/tui/index.tsx +0 -7
  86. package/src/web/App.tsx +0 -455
  87. package/src/web/components/activity/ActivityPage.tsx +0 -314
  88. package/src/web/components/activity/index.ts +0 -1
  89. package/src/web/components/agents/AgentCard.tsx +0 -189
  90. package/src/web/components/agents/AgentPanel.tsx +0 -2244
  91. package/src/web/components/agents/AgentsView.tsx +0 -180
  92. package/src/web/components/agents/CreateAgentModal.tsx +0 -475
  93. package/src/web/components/agents/index.ts +0 -4
  94. package/src/web/components/api/ApiDocsPage.tsx +0 -842
  95. package/src/web/components/auth/CreateAccountStep.tsx +0 -176
  96. package/src/web/components/auth/LoginPage.tsx +0 -91
  97. package/src/web/components/auth/index.ts +0 -2
  98. package/src/web/components/common/Icons.tsx +0 -250
  99. package/src/web/components/common/LoadingSpinner.tsx +0 -44
  100. package/src/web/components/common/Modal.tsx +0 -199
  101. package/src/web/components/common/Select.tsx +0 -97
  102. package/src/web/components/common/index.ts +0 -20
  103. package/src/web/components/connections/ConnectionsPage.tsx +0 -54
  104. package/src/web/components/connections/IntegrationsTab.tsx +0 -170
  105. package/src/web/components/connections/OverviewTab.tsx +0 -137
  106. package/src/web/components/connections/TriggersTab.tsx +0 -1346
  107. package/src/web/components/dashboard/Dashboard.tsx +0 -572
  108. package/src/web/components/dashboard/index.ts +0 -1
  109. package/src/web/components/index.ts +0 -21
  110. package/src/web/components/layout/ErrorBanner.tsx +0 -18
  111. package/src/web/components/layout/Header.tsx +0 -332
  112. package/src/web/components/layout/Sidebar.tsx +0 -231
  113. package/src/web/components/layout/index.ts +0 -3
  114. package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
  115. package/src/web/components/mcp/McpPage.tsx +0 -2515
  116. package/src/web/components/mcp/index.ts +0 -1
  117. package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
  118. package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
  119. package/src/web/components/onboarding/index.ts +0 -1
  120. package/src/web/components/settings/SettingsPage.tsx +0 -2776
  121. package/src/web/components/settings/index.ts +0 -1
  122. package/src/web/components/skills/SkillsPage.tsx +0 -1200
  123. package/src/web/components/tasks/TasksPage.tsx +0 -1116
  124. package/src/web/components/tasks/index.ts +0 -1
  125. package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
  126. package/src/web/components/tests/TestsPage.tsx +0 -594
  127. package/src/web/components/threads/ThreadsPage.tsx +0 -315
  128. package/src/web/context/AuthContext.tsx +0 -242
  129. package/src/web/context/ProjectContext.tsx +0 -214
  130. package/src/web/context/TelemetryContext.tsx +0 -299
  131. package/src/web/context/ThemeContext.tsx +0 -90
  132. package/src/web/context/UIModeContext.tsx +0 -49
  133. package/src/web/context/index.ts +0 -12
  134. package/src/web/hooks/index.ts +0 -3
  135. package/src/web/hooks/useAgents.ts +0 -115
  136. package/src/web/hooks/useOnboarding.ts +0 -20
  137. package/src/web/hooks/useProviders.ts +0 -75
  138. package/src/web/icon.png +0 -0
  139. package/src/web/index.html +0 -16
  140. package/src/web/styles.css +0 -118
  141. package/src/web/themes.ts +0 -162
  142. package/src/web/types.ts +0 -298
@@ -1,1346 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from "react";
2
- import { useAuth, useProjects } from "../../context";
3
- import { Select } from "../common";
4
-
5
- interface TriggerType {
6
- slug: string;
7
- name: string;
8
- description: string;
9
- type: "webhook" | "poll";
10
- toolkit_slug: string;
11
- toolkit_name: string;
12
- logo: string | null;
13
- config_schema: Record<string, unknown>;
14
- payload_schema: Record<string, unknown>;
15
- }
16
-
17
- interface TriggerInstance {
18
- id: string;
19
- trigger_slug: string;
20
- connected_account_id: string | null;
21
- status: "active" | "disabled";
22
- config: Record<string, unknown>;
23
- created_at: string;
24
- }
25
-
26
- interface Subscription {
27
- id: string;
28
- trigger_slug: string;
29
- trigger_instance_id: string | null;
30
- agent_id: string;
31
- enabled: boolean;
32
- project_id: string | null;
33
- created_at: string;
34
- updated_at: string;
35
- }
36
-
37
- interface ConnectedAccount {
38
- id: string;
39
- appId: string;
40
- appName: string;
41
- status: string;
42
- }
43
-
44
- interface IntegrationApp {
45
- id: string;
46
- name: string;
47
- slug: string;
48
- logo: string | null;
49
- }
50
-
51
- interface Agent {
52
- id: string;
53
- name: string;
54
- status: string;
55
- port: number | null;
56
- }
57
-
58
- interface TriggerProviderInfo {
59
- id: string;
60
- name: string;
61
- connected: boolean;
62
- }
63
-
64
- export function TriggersTab() {
65
- const { authFetch } = useAuth();
66
- const { currentProjectId } = useProjects();
67
-
68
- // Provider selection — only show configured providers
69
- const [providers, setProviders] = useState<TriggerProviderInfo[]>([]);
70
- const [selectedProvider, setSelectedProvider] = useState("");
71
-
72
- // Trigger instances (from selected provider)
73
- const [triggers, setTriggers] = useState<TriggerInstance[]>([]);
74
- const [triggersLoading, setTriggersLoading] = useState(true);
75
-
76
- // Subscriptions (local routing)
77
- const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
78
-
79
- // Browse trigger types
80
- const [triggerTypes, setTriggerTypes] = useState<TriggerType[]>([]);
81
- const [typesLoading, setTypesLoading] = useState(false);
82
- const [toolkitFilter, setToolkitFilter] = useState("");
83
- const [typeSearch, setTypeSearch] = useState("");
84
-
85
- // Create trigger
86
- const [showCreate, setShowCreate] = useState(false);
87
- const [selectedType, setSelectedType] = useState<TriggerType | null>(null);
88
- const [connectedAccounts, setConnectedAccounts] = useState<ConnectedAccount[]>([]);
89
- const [selectedAccountId, setSelectedAccountId] = useState("");
90
- const [creating, setCreating] = useState(false);
91
- const [createAgentId, setCreateAgentId] = useState(""); // For AgentDojo direct subscription flow
92
- const [browseConfig, setBrowseConfig] = useState<Record<string, string>>({});
93
- const [browseSelectedAccountId, setBrowseSelectedAccountId] = useState("");
94
-
95
- // AgentDojo add subscription modal
96
- const [showAddDojo, setShowAddDojo] = useState(false);
97
- const [dojoTriggerTypes, setDojoTriggerTypes] = useState<TriggerType[]>([]);
98
- const [dojoTypesLoading, setDojoTypesLoading] = useState(false);
99
- const [dojoAccounts, setDojoAccounts] = useState<ConnectedAccount[]>([]);
100
- const [dojoApps, setDojoApps] = useState<IntegrationApp[]>([]);
101
- const [dojoSelectedToolkit, setDojoSelectedToolkit] = useState("");
102
- const [dojoSelectedType, setDojoSelectedType] = useState<string>("");
103
- const [dojoAgentId, setDojoAgentId] = useState("");
104
- const [dojoCreating, setDojoCreating] = useState(false);
105
- const [dojoConfig, setDojoConfig] = useState<Record<string, string>>({});
106
- const [dojoSelectedAccountId, setDojoSelectedAccountId] = useState("");
107
- const [dojoAppDropdownOpen, setDojoAppDropdownOpen] = useState(false);
108
- const [dojoAppSearch, setDojoAppSearch] = useState("");
109
- const [dojoTriggerDropdownOpen, setDojoTriggerDropdownOpen] = useState(false);
110
- const [dojoTriggerSearch, setDojoTriggerSearch] = useState("");
111
-
112
- // Add subscription
113
- const [showAddSub, setShowAddSub] = useState(false);
114
- const [subTriggerId, setSubTriggerId] = useState("");
115
- const [subAgentId, setSubAgentId] = useState("");
116
- const [addingSub, setAddingSub] = useState(false);
117
-
118
- // Agents
119
- const [agents, setAgents] = useState<Agent[]>([]);
120
-
121
- const [error, setError] = useState<string | null>(null);
122
-
123
- const projectParam = currentProjectId && currentProjectId !== "unassigned" ? `?project_id=${currentProjectId}` : "";
124
-
125
- // Fetch available providers — only show ones with API keys configured
126
- const fetchProviders = useCallback(async () => {
127
- try {
128
- const res = await authFetch(`/api/triggers/providers${projectParam}`);
129
- if (res.ok) {
130
- const data = await res.json();
131
- const connected = (data.providers || []).filter((p: TriggerProviderInfo) => p.connected);
132
- setProviders(connected);
133
- // Auto-select first connected provider if none selected
134
- if (connected.length > 0) {
135
- setSelectedProvider(prev => {
136
- if (!prev || !connected.find((p: TriggerProviderInfo) => p.id === prev)) return connected[0].id;
137
- return prev;
138
- });
139
- }
140
- }
141
- } catch (e) {
142
- console.error("Failed to fetch providers:", e);
143
- }
144
- }, [authFetch]);
145
-
146
- // Fetch active triggers
147
- const fetchTriggers = useCallback(async () => {
148
- setTriggersLoading(true);
149
- try {
150
- const providerParam = `provider=${selectedProvider}`;
151
- const sep = projectParam ? "&" : "?";
152
- const url = projectParam
153
- ? `/api/triggers${projectParam}&${providerParam}`
154
- : `/api/triggers?${providerParam}`;
155
- const res = await authFetch(url);
156
- if (res.ok) {
157
- const data = await res.json();
158
- setTriggers(data.triggers || []);
159
- }
160
- } catch (e) {
161
- console.error("Failed to fetch triggers:", e);
162
- }
163
- setTriggersLoading(false);
164
- }, [authFetch, projectParam, selectedProvider]);
165
-
166
- // Fetch subscriptions
167
- const fetchSubscriptions = useCallback(async () => {
168
- try {
169
- const res = await authFetch(`/api/subscriptions${projectParam}`);
170
- if (res.ok) {
171
- const data = await res.json();
172
- setSubscriptions(data.subscriptions || []);
173
- }
174
- } catch (e) {
175
- console.error("Failed to fetch subscriptions:", e);
176
- }
177
- }, [authFetch, projectParam]);
178
-
179
- // Fetch agents (project-scoped)
180
- const fetchAgents = useCallback(async () => {
181
- try {
182
- const res = await authFetch(`/api/agents${projectParam}`);
183
- if (res.ok) {
184
- const data = await res.json();
185
- setAgents(data.agents || []);
186
- }
187
- } catch (e) {
188
- // Ignore
189
- }
190
- }, [authFetch, projectParam]);
191
-
192
- useEffect(() => {
193
- fetchProviders();
194
- fetchTriggers();
195
- fetchSubscriptions();
196
- fetchAgents();
197
- }, [fetchProviders, fetchTriggers, fetchSubscriptions, fetchAgents]);
198
-
199
- // Browse trigger types
200
- const browseTriggerTypes = async (toolkit?: string) => {
201
- setTypesLoading(true);
202
- try {
203
- let url = `/api/triggers/types?provider=${selectedProvider}`;
204
- if (toolkit) url += `&toolkit_slugs=${toolkit}`;
205
- if (currentProjectId && currentProjectId !== "unassigned") url += `&project_id=${currentProjectId}`;
206
- const res = await authFetch(url);
207
- if (res.ok) {
208
- const data = await res.json();
209
- setTriggerTypes(data.types || []);
210
- } else {
211
- const data = await res.json();
212
- setError(data.error || "Failed to fetch trigger types");
213
- }
214
- } catch (e) {
215
- setError("Failed to fetch trigger types");
216
- }
217
- setTypesLoading(false);
218
- };
219
-
220
- // Fetch connected accounts when creating
221
- const fetchConnectedAccounts = async () => {
222
- try {
223
- const res = await authFetch(`/api/integrations/${selectedProvider}/connected${projectParam}`);
224
- if (res.ok) {
225
- const data = await res.json();
226
- setConnectedAccounts((data.accounts || []).filter((a: ConnectedAccount) => a.status === "active"));
227
- }
228
- } catch (e) {
229
- // Ignore
230
- }
231
- };
232
-
233
- // Start create flow
234
- const startCreate = (triggerType: TriggerType) => {
235
- setSelectedType(triggerType);
236
- setSelectedAccountId("");
237
- setCreateAgentId("");
238
- setBrowseConfig({});
239
- setBrowseSelectedAccountId("");
240
- setShowCreate(true);
241
- fetchConnectedAccounts();
242
- };
243
-
244
- const isAgentDojo = selectedProvider === "agentdojo";
245
-
246
- // Open AgentDojo add subscription modal — fetches from agentdojo provider (same as Integrations tab)
247
- const openAddDojoSub = async () => {
248
- setShowAddDojo(true);
249
- setDojoSelectedType("");
250
- setDojoSelectedToolkit("");
251
- setDojoAgentId("");
252
- setDojoConfig({});
253
- setDojoSelectedAccountId("");
254
-
255
- const loadTypes = async () => {
256
- if (dojoTriggerTypes.length > 0) return;
257
- setDojoTypesLoading(true);
258
- try {
259
- let url = `/api/triggers/types?provider=agentdojo`;
260
- if (currentProjectId && currentProjectId !== "unassigned") url += `&project_id=${currentProjectId}`;
261
- const res = await authFetch(url);
262
- const data = await res.json();
263
- setDojoTriggerTypes(data.types || []);
264
- } catch (e) {
265
- console.error("Failed to load trigger types:", e);
266
- }
267
- setDojoTypesLoading(false);
268
- };
269
-
270
- const loadAccounts = async () => {
271
- try {
272
- const url = `/api/integrations/agentdojo/connected${projectParam}`;
273
- const res = await authFetch(url);
274
- const data = await res.json();
275
- const active = (data.accounts || []).filter((a: ConnectedAccount) => a.status === "active");
276
- setDojoAccounts(active);
277
- } catch (e) {
278
- console.error("Failed to load connected accounts:", e);
279
- }
280
- };
281
-
282
- const loadApps = async () => {
283
- if (dojoApps.length > 0) return;
284
- try {
285
- const url = `/api/integrations/agentdojo/apps${projectParam}`;
286
- const res = await authFetch(url);
287
- const data = await res.json();
288
- setDojoApps((data.apps || []).map((a: any) => ({ id: a.id, name: a.name, slug: a.slug, logo: a.logo })));
289
- } catch (e) {
290
- console.error("Failed to load apps:", e);
291
- }
292
- };
293
-
294
- await Promise.all([loadTypes(), loadAccounts(), loadApps()]);
295
- };
296
-
297
- // Create AgentDojo subscription from the add-subscription modal
298
- const handleAddDojoSub = async () => {
299
- const tt = dojoTriggerTypes.find(t => t.slug === dojoSelectedType);
300
- // Use derived dojoMatchedAccount (respects user dropdown selection + auto-match fallback)
301
- const matched = dojoMatchedAccount;
302
- if (!tt || !dojoAgentId || !matched) return;
303
-
304
- setDojoCreating(true);
305
- setError(null);
306
- try {
307
- const agent = agents.find(a => a.id === dojoAgentId);
308
- const providerParam = `provider=agentdojo`;
309
- const url = projectParam
310
- ? `/api/triggers${projectParam}&${providerParam}`
311
- : `/api/triggers?${providerParam}`;
312
- const configPayload = {
313
- callback_url: `${window.location.origin}/api/webhooks/agentdojo`,
314
- title: `${tt.name} → ${agent?.name || "Agent"}`,
315
- server: tt.toolkit_slug,
316
- agent_id: dojoAgentId,
317
- ...dojoConfig,
318
- };
319
- const res = await authFetch(url, {
320
- method: "POST",
321
- headers: { "Content-Type": "application/json" },
322
- body: JSON.stringify({
323
- slug: tt.slug,
324
- connectedAccountId: matched.id,
325
- config: configPayload,
326
- }),
327
- });
328
- const data = await res.json();
329
- if (!res.ok) {
330
- setError(data.error || "Failed to create subscription");
331
- } else {
332
- setShowAddDojo(false);
333
- fetchTriggers();
334
- }
335
- } catch (e: any) {
336
- setError(e.message || "Failed to create subscription");
337
- }
338
- setDojoCreating(false);
339
- };
340
-
341
- // Create trigger (Composio: trigger instance, AgentDojo: subscription + agent routing)
342
- const handleCreate = async () => {
343
- if (!selectedType) return;
344
-
345
- // AgentDojo: create remote subscription directly (callback_url points to apteva webhook handler)
346
- if (isAgentDojo) {
347
- if (!createAgentId || !browseMatchedAccount) return;
348
- setCreating(true);
349
- setError(null);
350
- try {
351
- const agent = agents.find(a => a.id === createAgentId);
352
- const instanceUrl = window.location.origin;
353
- const providerParam = `provider=${selectedProvider}`;
354
- const url = projectParam
355
- ? `/api/triggers${projectParam}&${providerParam}`
356
- : `/api/triggers?${providerParam}`;
357
- const res = await authFetch(url, {
358
- method: "POST",
359
- headers: { "Content-Type": "application/json" },
360
- body: JSON.stringify({
361
- slug: selectedType.slug,
362
- connectedAccountId: browseMatchedAccount.id,
363
- config: {
364
- callback_url: `${instanceUrl}/api/webhooks/agentdojo`,
365
- title: `${selectedType.name} → ${agent?.name || "Agent"}`,
366
- server: selectedType.toolkit_slug,
367
- agent_id: createAgentId,
368
- ...browseConfig, // Dynamic config fields (e.g. owner, repo)
369
- },
370
- }),
371
- });
372
- const data = await res.json();
373
- if (!res.ok) {
374
- setError(data.error || "Failed to create subscription");
375
- } else {
376
- setShowCreate(false);
377
- setSelectedType(null);
378
- fetchTriggers();
379
- }
380
- } catch (e: any) {
381
- setError(e.message || "Failed to create subscription");
382
- }
383
- setCreating(false);
384
- return;
385
- }
386
-
387
- // Composio: standard trigger instance creation
388
- if (!selectedAccountId) return;
389
- setCreating(true);
390
- setError(null);
391
- try {
392
- const providerParam = `provider=${selectedProvider}`;
393
- const url = projectParam
394
- ? `/api/triggers${projectParam}&${providerParam}`
395
- : `/api/triggers?${providerParam}`;
396
- const res = await authFetch(url, {
397
- method: "POST",
398
- headers: { "Content-Type": "application/json" },
399
- body: JSON.stringify({
400
- slug: selectedType.slug,
401
- connectedAccountId: selectedAccountId,
402
- }),
403
- });
404
- const data = await res.json();
405
- if (!res.ok) {
406
- setError(data.error || "Failed to create trigger");
407
- } else {
408
- setShowCreate(false);
409
- setSelectedType(null);
410
- fetchTriggers();
411
- }
412
- } catch (e: any) {
413
- setError(e.message || "Failed to create trigger");
414
- }
415
- setCreating(false);
416
- };
417
-
418
- // Enable/disable trigger
419
- const toggleTrigger = async (triggerId: string, currentStatus: string) => {
420
- const action = currentStatus === "active" ? "disable" : "enable";
421
- try {
422
- const providerQ = projectParam ? `&provider=${selectedProvider}` : `?provider=${selectedProvider}`;
423
- const res = await authFetch(`/api/triggers/${triggerId}/${action}${projectParam}${providerQ}`, {
424
- method: "POST",
425
- });
426
- if (res.ok) {
427
- fetchTriggers();
428
- } else {
429
- const data = await res.json();
430
- setError(data.error || `Failed to ${action} trigger`);
431
- }
432
- } catch (e) {
433
- setError(`Failed to ${action} trigger`);
434
- }
435
- };
436
-
437
- // Delete trigger
438
- const deleteTrigger = async (triggerId: string) => {
439
- try {
440
- const providerQ = projectParam ? `&provider=${selectedProvider}` : `?provider=${selectedProvider}`;
441
- const res = await authFetch(`/api/triggers/${triggerId}${projectParam}${providerQ}`, {
442
- method: "DELETE",
443
- });
444
- if (res.ok) {
445
- fetchTriggers();
446
- } else {
447
- const data = await res.json();
448
- setError(data.error || "Failed to delete trigger");
449
- }
450
- } catch (e) {
451
- setError("Failed to delete trigger");
452
- }
453
- };
454
-
455
- // Add subscription — backend auto-creates webhook if needed
456
- const handleAddSubscription = async () => {
457
- if (!subTriggerId || !subAgentId) return;
458
-
459
- // Find the trigger instance to get its slug
460
- const trigger = triggers.find(t => t.id === subTriggerId);
461
- if (!trigger) return;
462
-
463
- setAddingSub(true);
464
- setError(null);
465
- try {
466
- const res = await authFetch(`/api/subscriptions`, {
467
- method: "POST",
468
- headers: { "Content-Type": "application/json" },
469
- body: JSON.stringify({
470
- trigger_slug: trigger.trigger_slug,
471
- trigger_instance_id: trigger.id,
472
- agent_id: subAgentId,
473
- provider: selectedProvider,
474
- project_id: currentProjectId && currentProjectId !== "unassigned" ? currentProjectId : null,
475
- public_url: window.location.origin,
476
- }),
477
- });
478
- const data = await res.json();
479
- if (!res.ok) {
480
- setError(data.error || "Failed to create subscription");
481
- } else {
482
- setShowAddSub(false);
483
- setSubTriggerId("");
484
- setSubAgentId("");
485
- fetchSubscriptions();
486
- }
487
- } catch (e: any) {
488
- setError(e.message || "Failed to create subscription");
489
- }
490
- setAddingSub(false);
491
- };
492
-
493
- // Toggle subscription
494
- const toggleSubscription = async (sub: Subscription) => {
495
- const action = sub.enabled ? "disable" : "enable";
496
- try {
497
- const res = await authFetch(`/api/subscriptions/${sub.id}/${action}`, {
498
- method: "POST",
499
- });
500
- if (res.ok) fetchSubscriptions();
501
- } catch (e) {
502
- setError(`Failed to ${action} subscription`);
503
- }
504
- };
505
-
506
- // Delete subscription
507
- const deleteSubscription = async (id: string) => {
508
- try {
509
- const res = await authFetch(`/api/subscriptions/${id}`, {
510
- method: "DELETE",
511
- });
512
- if (res.ok) fetchSubscriptions();
513
- } catch (e) {
514
- setError("Failed to delete subscription");
515
- }
516
- };
517
-
518
- // Filter trigger types by search
519
- const filteredTypes = triggerTypes.filter(t => {
520
- if (!typeSearch) return true;
521
- const s = typeSearch.toLowerCase();
522
- return t.name.toLowerCase().includes(s) || t.slug.toLowerCase().includes(s) || t.description.toLowerCase().includes(s);
523
- });
524
-
525
- // Best-effort match connected account from toolkit slug
526
- // A single credential can serve multiple toolkits (e.g. "OmniKit Platform" for "OmniKit Messaging")
527
- const matchAccount = (accounts: ConnectedAccount[], toolkitSlug: string): ConnectedAccount | null => {
528
- if (!toolkitSlug || accounts.length === 0) return null;
529
- const slug = toolkitSlug.toLowerCase().replace(/[-_]/g, " ");
530
- // Exact match
531
- const exact = accounts.find(a =>
532
- a.appId?.toLowerCase() === toolkitSlug.toLowerCase() ||
533
- a.appName?.toLowerCase() === toolkitSlug.toLowerCase()
534
- );
535
- if (exact) return exact;
536
- // Contains match
537
- const contains = accounts.find(a =>
538
- a.appId?.toLowerCase().includes(slug) ||
539
- a.appName?.toLowerCase().replace(/[-_]/g, " ").includes(slug) ||
540
- slug.includes(a.appId?.toLowerCase() || "") ||
541
- slug.includes(a.appName?.toLowerCase().replace(/[-_]/g, " ") || "")
542
- );
543
- if (contains) return contains;
544
- // Prefix match — first word overlap (e.g. "omnikit" matches "omnikit platform" and "omnikit messaging")
545
- const slugWords = slug.split(/\s+/);
546
- return accounts.find(a => {
547
- const nameWords = (a.appName || "").toLowerCase().replace(/[-_]/g, " ").split(/\s+/);
548
- return slugWords[0] && nameWords[0] && slugWords[0] === nameWords[0];
549
- }) || null;
550
- };
551
-
552
- // Derived: auto-matched or user-selected account for Add Subscription modal
553
- const dojoSelectedTriggerType = dojoTriggerTypes.find(t => t.slug === dojoSelectedType);
554
- const dojoAutoMatch = dojoSelectedTriggerType ? matchAccount(dojoAccounts, dojoSelectedTriggerType.toolkit_slug) : null;
555
- const dojoMatchedAccount = dojoSelectedAccountId
556
- ? dojoAccounts.find(a => a.id === dojoSelectedAccountId) || dojoAutoMatch
557
- : dojoAutoMatch;
558
-
559
- // Derived: auto-matched or user-selected account for Browse Subscribe modal
560
- const browseAutoMatch = selectedType && isAgentDojo ? matchAccount(connectedAccounts, selectedType.toolkit_slug) : null;
561
- const browseMatchedAccount = browseSelectedAccountId
562
- ? connectedAccounts.find(a => a.id === browseSelectedAccountId) || browseAutoMatch
563
- : browseAutoMatch;
564
-
565
- // Derived: group trigger types by toolkit, enriched with logos from apps list
566
- const dojoToolkits = React.useMemo(() => {
567
- const appLogos = new Map<string, string>();
568
- for (const app of dojoApps) {
569
- if (app.logo) appLogos.set(app.slug, app.logo);
570
- }
571
- const map = new Map<string, { slug: string; name: string; logo: string | null; count: number }>();
572
- for (const t of dojoTriggerTypes) {
573
- const existing = map.get(t.toolkit_slug);
574
- if (existing) {
575
- existing.count++;
576
- } else {
577
- const logo = appLogos.get(t.toolkit_slug) || t.logo || null;
578
- map.set(t.toolkit_slug, { slug: t.toolkit_slug, name: t.toolkit_name, logo, count: 1 });
579
- }
580
- }
581
- return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name));
582
- }, [dojoTriggerTypes, dojoApps]);
583
-
584
- // Derived: triggers for the selected toolkit
585
- const dojoToolkitTriggers = dojoSelectedToolkit
586
- ? dojoTriggerTypes.filter(t => t.toolkit_slug === dojoSelectedToolkit)
587
- : [];
588
-
589
- // Derived: selected toolkit info (for logo in trigger dropdown)
590
- const dojoSelectedToolkitInfo = dojoToolkits.find(t => t.slug === dojoSelectedToolkit);
591
-
592
- // Agent map for quick lookups
593
- const agentMap = new Map(agents.map(a => [a.id, a]));
594
-
595
- if (providers.length === 0 && !triggersLoading) {
596
- return (
597
- <div className="bg-[var(--color-surface)] card p-8 text-center">
598
- <p className="text-[var(--color-text-muted)]">No trigger providers configured.</p>
599
- <p className="text-sm text-[var(--color-text-faint)] mt-1">Add API keys for Composio or AgentDojo in Settings to enable triggers.</p>
600
- </div>
601
- );
602
- }
603
-
604
- return (
605
- <div className="space-y-6">
606
- {/* Error */}
607
- {error && (
608
- <div className="text-red-400 text-sm p-3 bg-red-500/10 border border-red-500/20 rounded-lg flex items-center justify-between">
609
- <span>{error}</span>
610
- <button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">x</button>
611
- </div>
612
- )}
613
-
614
- {/* Provider Selector — only show if multiple configured */}
615
- {providers.length > 1 && (
616
- <div className="flex items-center gap-2">
617
- <span className="text-xs text-[var(--color-text-muted)]">Provider:</span>
618
- <div className="flex gap-1 bg-[var(--color-surface)] card p-0.5">
619
- {providers.map(p => (
620
- <button
621
- key={p.id}
622
- onClick={() => {
623
- setSelectedProvider(p.id);
624
- setTriggerTypes([]);
625
- setToolkitFilter("");
626
- setTypeSearch("");
627
- }}
628
- className={`px-3 py-1 rounded text-xs font-medium transition ${
629
- selectedProvider === p.id
630
- ? "bg-[var(--color-surface-raised)] text-white"
631
- : "text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
632
- }`}
633
- >
634
- {p.name}
635
- </button>
636
- ))}
637
- </div>
638
- </div>
639
- )}
640
-
641
- {/* Subscriptions (trigger → agent routing) — hide entirely for AgentDojo (handled in Active Subscriptions) */}
642
- {!isAgentDojo && (
643
- <section>
644
- <div className="flex items-center justify-between mb-3">
645
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)]">
646
- Subscriptions ({subscriptions.length})
647
- </h3>
648
- <button
649
- onClick={() => setShowAddSub(true)}
650
- className="text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] px-3 py-1.5 rounded transition"
651
- >
652
- + Add Subscription
653
- </button>
654
- </div>
655
-
656
- {subscriptions.length === 0 ? (
657
- <div className="bg-[var(--color-surface)] card p-6 text-center text-[var(--color-text-muted)] text-sm">
658
- No subscriptions yet. Add one to route trigger events to an agent.
659
- </div>
660
- ) : (
661
- <div className="space-y-2">
662
- {subscriptions.map(sub => {
663
- const agent = agentMap.get(sub.agent_id);
664
- return (
665
- <div key={sub.id} className="bg-[var(--color-surface)] card p-3 flex items-center gap-3">
666
- <div className={`w-2 h-2 rounded-full flex-shrink-0 ${sub.enabled ? "bg-green-400" : "bg-[var(--color-text-muted)]"}`} />
667
- <div className="flex-1 min-w-0">
668
- <div className="text-sm font-medium truncate">
669
- {sub.trigger_slug.replace(/_/g, " ")}
670
- <span className="text-[var(--color-text-faint)] mx-1.5">&rarr;</span>
671
- <span className="text-[var(--color-accent)]">{agent?.name || "Unknown Agent"}</span>
672
- </div>
673
- <div className="text-xs text-[var(--color-text-muted)]">
674
- {sub.trigger_instance_id
675
- ? `Instance: ${sub.trigger_instance_id.slice(0, 12)}...`
676
- : "All instances"
677
- }
678
- </div>
679
- </div>
680
- <div className="flex items-center gap-2 flex-shrink-0">
681
- <button
682
- onClick={() => toggleSubscription(sub)}
683
- className={`text-xs px-3 py-1 rounded transition ${
684
- sub.enabled
685
- ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
686
- : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
687
- }`}
688
- >
689
- {sub.enabled ? "Disable" : "Enable"}
690
- </button>
691
- <button
692
- onClick={() => deleteSubscription(sub.id)}
693
- className="text-xs text-[var(--color-text-muted)] hover:text-red-400 transition px-2"
694
- >
695
- Delete
696
- </button>
697
- </div>
698
- </div>
699
- );
700
- })}
701
- </div>
702
- )}
703
- </section>
704
- )}
705
-
706
- {/* Trigger Instances — only show for providers that have them (not AgentDojo) */}
707
- {!isAgentDojo && (
708
- <section>
709
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">
710
- Trigger Instances ({triggers.length})
711
- </h3>
712
- {triggersLoading ? (
713
- <div className="text-center py-6 text-[var(--color-text-muted)] text-sm">Loading triggers...</div>
714
- ) : triggers.length === 0 ? (
715
- <div className="bg-[var(--color-surface)] card p-6 text-center text-[var(--color-text-muted)] text-sm">
716
- No trigger instances. Browse trigger types below to create one.
717
- </div>
718
- ) : (
719
- <div className="space-y-2">
720
- {triggers.map(trigger => (
721
- <div key={trigger.id} className="bg-[var(--color-surface)] card p-3 flex items-center gap-3">
722
- <div className={`w-2 h-2 rounded-full flex-shrink-0 ${trigger.status === "active" ? "bg-green-400" : "bg-[var(--color-text-muted)]"}`} />
723
- <div className="flex-1 min-w-0">
724
- <div className="text-sm font-medium truncate">
725
- {trigger.trigger_slug.replace(/_/g, " ")}
726
- </div>
727
- <div className="text-xs text-[var(--color-text-muted)]">
728
- ID: {trigger.id.slice(0, 12)}... | Created: {new Date(trigger.created_at).toLocaleDateString()}
729
- </div>
730
- </div>
731
- <div className="flex items-center gap-2 flex-shrink-0">
732
- <button
733
- onClick={() => toggleTrigger(trigger.id, trigger.status)}
734
- className={`text-xs px-3 py-1 rounded transition ${
735
- trigger.status === "active"
736
- ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
737
- : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
738
- }`}
739
- >
740
- {trigger.status === "active" ? "Disable" : "Enable"}
741
- </button>
742
- <button
743
- onClick={() => deleteTrigger(trigger.id)}
744
- className="text-xs text-[var(--color-text-muted)] hover:text-red-400 transition px-2"
745
- >
746
- Delete
747
- </button>
748
- </div>
749
- </div>
750
- ))}
751
- </div>
752
- )}
753
- </section>
754
- )}
755
-
756
- {/* AgentDojo Active Subscriptions — shows remote subscriptions directly */}
757
- {isAgentDojo && (
758
- <section>
759
- <div className="flex items-center justify-between mb-3">
760
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)]">
761
- Active Subscriptions ({triggers.length})
762
- </h3>
763
- <button
764
- onClick={openAddDojoSub}
765
- className="text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] px-3 py-1.5 rounded transition"
766
- >
767
- + Add Subscription
768
- </button>
769
- </div>
770
- {triggersLoading ? (
771
- <div className="text-center py-6 text-[var(--color-text-muted)] text-sm">Loading subscriptions...</div>
772
- ) : triggers.length === 0 ? (
773
- <div className="bg-[var(--color-surface)] card p-6 text-center text-[var(--color-text-muted)] text-sm">
774
- No active subscriptions. Browse trigger types below to create one.
775
- </div>
776
- ) : (
777
- <div className="space-y-2">
778
- {triggers.map(trigger => {
779
- const localSub = subscriptions.find(s => s.trigger_instance_id === trigger.id);
780
- const agent = localSub ? agentMap.get(localSub.agent_id) : null;
781
- return (
782
- <div key={trigger.id} className="bg-[var(--color-surface)] card p-3 flex items-center gap-3">
783
- <div className={`w-2 h-2 rounded-full flex-shrink-0 ${trigger.status === "active" ? "bg-green-400" : "bg-[var(--color-text-muted)]"}`} />
784
- <div className="flex-1 min-w-0">
785
- <div className="text-sm font-medium truncate">
786
- {(trigger.config?.title as string) || trigger.trigger_slug.replace(/_/g, " ")}
787
- {agent && (
788
- <>
789
- <span className="text-[var(--color-text-faint)] mx-1.5">&rarr;</span>
790
- <span className="text-[var(--color-accent)]">{agent.name}</span>
791
- </>
792
- )}
793
- </div>
794
- <div className="text-xs text-[var(--color-text-muted)]">
795
- {trigger.config?.server && <span>{String(trigger.config.server)} | </span>}
796
- ID: {String(trigger.id).slice(0, 8)} | Created: {new Date(trigger.created_at).toLocaleDateString()}
797
- </div>
798
- </div>
799
- <div className="flex items-center gap-2 flex-shrink-0">
800
- <button
801
- onClick={() => toggleTrigger(trigger.id, trigger.status)}
802
- className={`text-xs px-3 py-1 rounded transition ${
803
- trigger.status === "active"
804
- ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
805
- : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
806
- }`}
807
- >
808
- {trigger.status === "active" ? "Disable" : "Enable"}
809
- </button>
810
- <button
811
- onClick={() => deleteTrigger(trigger.id)}
812
- className="text-xs text-[var(--color-text-muted)] hover:text-red-400 transition px-2"
813
- >
814
- Delete
815
- </button>
816
- </div>
817
- </div>
818
- );
819
- })}
820
- </div>
821
- )}
822
- </section>
823
- )}
824
-
825
- {/* Browse Trigger Types */}
826
- <section>
827
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">Browse Trigger Types</h3>
828
- <div className="flex gap-2 mb-3">
829
- <input
830
- type="text"
831
- value={toolkitFilter}
832
- onChange={(e) => setToolkitFilter(e.target.value)}
833
- placeholder="Toolkit filter (e.g. github, gmail, slack)"
834
- className="flex-1 bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
835
- />
836
- <button
837
- onClick={() => browseTriggerTypes(toolkitFilter || undefined)}
838
- disabled={typesLoading}
839
- className="text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] px-4 py-2 rounded transition disabled:opacity-50"
840
- >
841
- {typesLoading ? "Loading..." : "Browse"}
842
- </button>
843
- </div>
844
-
845
- {triggerTypes.length > 0 && (
846
- <>
847
- <input
848
- type="text"
849
- value={typeSearch}
850
- onChange={(e) => setTypeSearch(e.target.value)}
851
- placeholder="Search trigger types..."
852
- className="w-full bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm mb-3 focus:outline-none focus:border-[var(--color-accent)]"
853
- />
854
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
855
- {filteredTypes.slice(0, 30).map(tt => (
856
- <div key={tt.slug} className="bg-[var(--color-surface)] border border-[var(--color-border)] hover:border-[var(--color-border-light)] rounded-lg p-3 transition">
857
- <div className="flex items-start gap-3">
858
- {tt.logo ? (
859
- <img src={tt.logo} alt={tt.toolkit_name} className="w-8 h-8 rounded object-contain flex-shrink-0" />
860
- ) : (
861
- <div className="w-8 h-8 rounded bg-[var(--color-surface-raised)] flex items-center justify-center text-xs flex-shrink-0">
862
- {tt.toolkit_name?.[0]?.toUpperCase() || "?"}
863
- </div>
864
- )}
865
- <div className="flex-1 min-w-0">
866
- <div className="text-sm font-medium truncate">{tt.name}</div>
867
- <div className="text-xs text-[var(--color-text-muted)]">{tt.toolkit_name}</div>
868
- <div className="text-xs text-[var(--color-text-faint)] mt-1 line-clamp-2">{tt.description}</div>
869
- </div>
870
- </div>
871
- <button
872
- onClick={() => startCreate(tt)}
873
- className="w-full mt-3 text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] px-3 py-1.5 rounded transition"
874
- >
875
- {isAgentDojo ? "Subscribe" : "Create Trigger"}
876
- </button>
877
- </div>
878
- ))}
879
- </div>
880
- {filteredTypes.length > 30 && (
881
- <p className="text-xs text-[var(--color-text-faint)] mt-3 text-center">
882
- Showing first 30 of {filteredTypes.length} types. Use search to filter.
883
- </p>
884
- )}
885
- </>
886
- )}
887
- </section>
888
-
889
- {/* Create Trigger Modal */}
890
- {showCreate && selectedType && (
891
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
892
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
893
- <h3 className="font-medium mb-1">
894
- {isAgentDojo ? "Create Subscription" : "Create Trigger"}
895
- </h3>
896
- <p className="text-xs text-[var(--color-text-muted)] mb-4">
897
- {selectedType.name}
898
- {selectedType.toolkit_name && <span className="text-[var(--color-text-faint)]"> ({selectedType.toolkit_name})</span>}
899
- </p>
900
-
901
- <div className="space-y-4">
902
- {/* Connected Account — only for Composio */}
903
- {!isAgentDojo && (
904
- <div>
905
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Connected Account</label>
906
- {connectedAccounts.length === 0 ? (
907
- <div className="text-xs text-[var(--color-text-muted)] bg-[var(--color-bg)] rounded p-3">
908
- No connected accounts available. Connect an app first in the Integrations tab.
909
- </div>
910
- ) : (
911
- <Select
912
- value={selectedAccountId}
913
- onChange={setSelectedAccountId}
914
- placeholder="Select account..."
915
- options={connectedAccounts.map(acc => ({
916
- value: acc.id,
917
- label: `${acc.appName} (${acc.id.slice(0, 8)}...)`,
918
- }))}
919
- />
920
- )}
921
- </div>
922
- )}
923
-
924
- {/* Agent selection — for AgentDojo direct subscription */}
925
- {isAgentDojo && (
926
- <div>
927
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Route to Agent</label>
928
- {agents.length === 0 ? (
929
- <div className="text-xs text-[var(--color-text-muted)] bg-[var(--color-bg)] rounded p-3">
930
- No agents available. Create an agent first.
931
- </div>
932
- ) : (
933
- <Select
934
- value={createAgentId}
935
- onChange={setCreateAgentId}
936
- placeholder="Select agent..."
937
- options={agents.map(agent => ({
938
- value: agent.id,
939
- label: `${agent.name} (${agent.status})`,
940
- }))}
941
- />
942
- )}
943
-
944
- {/* Connected account — auto-matched from toolkit */}
945
- <div className="mt-3">
946
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Connected Account</label>
947
- {browseMatchedAccount ? (
948
- <div className="text-xs text-green-400 bg-green-500/10 border border-green-500/20 rounded p-3">
949
- Connected: {browseMatchedAccount.appName}
950
- </div>
951
- ) : (
952
- <div className="text-xs text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded p-3">
953
- No connected account for {selectedType?.toolkit_name || "this app"}. Connect it first in the Integrations tab.
954
- </div>
955
- )}
956
- </div>
957
-
958
- {/* Dynamic config fields from config_schema */}
959
- {selectedType.config_schema && Object.keys((selectedType.config_schema as any).properties || {}).length > 0 && (
960
- <div className="mt-3">
961
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Configuration</label>
962
- <div className="space-y-2">
963
- {Object.entries((selectedType.config_schema as any).properties || {}).map(([key, schema]: [string, any]) => {
964
- const required = ((selectedType.config_schema as any).required || []).includes(key);
965
- return (
966
- <div key={key}>
967
- <label className="block text-[11px] text-[var(--color-text-secondary)] mb-1">
968
- {schema.title || key}
969
- {required && <span className="text-red-400 ml-0.5">*</span>}
970
- </label>
971
- <input
972
- type="text"
973
- value={browseConfig[key] || ""}
974
- onChange={(e) => setBrowseConfig(prev => ({ ...prev, [key]: e.target.value }))}
975
- placeholder={schema.description || `Enter ${schema.title || key}...`}
976
- className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
977
- />
978
- </div>
979
- );
980
- })}
981
- </div>
982
- </div>
983
- )}
984
- </div>
985
- )}
986
- </div>
987
-
988
- <div className="flex gap-2 mt-4">
989
- <button
990
- onClick={() => { setShowCreate(false); setSelectedType(null); }}
991
- className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
992
- >
993
- Cancel
994
- </button>
995
- <button
996
- onClick={handleCreate}
997
- disabled={isAgentDojo ? (
998
- !createAgentId || !browseMatchedAccount || creating ||
999
- (selectedType?.config_schema && ((selectedType.config_schema as any).required || []).some((key: string) => !browseConfig[key]?.trim()))
1000
- ) : (!selectedAccountId || creating)}
1001
- className="flex-1 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition disabled:opacity-50"
1002
- >
1003
- {creating ? "Creating..." : isAgentDojo ? "Subscribe" : "Create"}
1004
- </button>
1005
- </div>
1006
- </div>
1007
- </div>
1008
- )}
1009
-
1010
- {/* Add Subscription Modal */}
1011
- {showAddSub && (
1012
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1013
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
1014
- <h3 className="font-medium mb-1">Route Trigger to Agent</h3>
1015
- <p className="text-xs text-[var(--color-text-muted)] mb-4">
1016
- {triggers.length === 0
1017
- ? "No trigger instances yet. Create one first from the Browse section below."
1018
- : "Select a trigger instance and the agent that should handle its events."
1019
- }
1020
- </p>
1021
-
1022
- {triggers.length > 0 ? (
1023
- <>
1024
- <div className="space-y-4">
1025
- <div>
1026
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Trigger Instance</label>
1027
- <Select
1028
- value={subTriggerId}
1029
- onChange={setSubTriggerId}
1030
- placeholder="Select trigger..."
1031
- options={triggers.map(t => ({
1032
- value: t.id,
1033
- label: `${t.trigger_slug.replace(/_/g, " ")}`,
1034
- }))}
1035
- />
1036
- {subTriggerId && (
1037
- <div className="text-xs text-[var(--color-text-faint)] mt-1 font-mono">
1038
- ID: {subTriggerId.slice(0, 16)}...
1039
- </div>
1040
- )}
1041
- </div>
1042
-
1043
- <div>
1044
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Target Agent</label>
1045
- <Select
1046
- value={subAgentId}
1047
- onChange={setSubAgentId}
1048
- placeholder="Select agent..."
1049
- options={agents.map(agent => ({
1050
- value: agent.id,
1051
- label: `${agent.name} (${agent.status})`,
1052
- }))}
1053
- />
1054
- </div>
1055
- </div>
1056
-
1057
- <div className="flex gap-2 mt-5">
1058
- <button
1059
- onClick={() => { setShowAddSub(false); setSubTriggerId(""); setSubAgentId(""); }}
1060
- className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
1061
- >
1062
- Cancel
1063
- </button>
1064
- <button
1065
- onClick={handleAddSubscription}
1066
- disabled={!subTriggerId || !subAgentId || addingSub}
1067
- className="flex-1 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition disabled:opacity-50"
1068
- >
1069
- {addingSub ? "Adding..." : "Add"}
1070
- </button>
1071
- </div>
1072
- </>
1073
- ) : (
1074
- <div className="flex gap-2 mt-4">
1075
- <button
1076
- onClick={() => setShowAddSub(false)}
1077
- className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
1078
- >
1079
- Close
1080
- </button>
1081
- </div>
1082
- )}
1083
- </div>
1084
- </div>
1085
- )}
1086
-
1087
- {/* AgentDojo Add Subscription Modal */}
1088
- {showAddDojo && (
1089
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1090
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-lg mx-4">
1091
- <h3 className="font-medium mb-1">Add Subscription</h3>
1092
- <p className="text-xs text-[var(--color-text-muted)] mb-4">
1093
- Select an app and trigger, then route it to an agent.
1094
- </p>
1095
-
1096
- {dojoTypesLoading ? (
1097
- <div className="text-center py-8 text-[var(--color-text-muted)] text-sm">Loading...</div>
1098
- ) : dojoTriggerTypes.length === 0 ? (
1099
- <div className="text-center py-8 text-[var(--color-text-muted)] text-sm">
1100
- No triggers available. Connect an app first in the Integrations tab.
1101
- </div>
1102
- ) : (
1103
- <div className="space-y-4">
1104
- {/* App selector — custom dropdown with logos */}
1105
- <div>
1106
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">App</label>
1107
- <div className="relative">
1108
- <button
1109
- onClick={() => { setDojoAppDropdownOpen(!dojoAppDropdownOpen); setDojoTriggerDropdownOpen(false); setDojoAppSearch(""); }}
1110
- className="w-full flex items-center gap-2 bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm text-left hover:border-[var(--color-text-faint)] transition"
1111
- >
1112
- {dojoSelectedToolkitInfo ? (
1113
- <>
1114
- {dojoSelectedToolkitInfo.logo ? (
1115
- <img src={dojoSelectedToolkitInfo.logo} alt="" className="w-5 h-5 rounded object-contain flex-shrink-0" />
1116
- ) : (
1117
- <div className="w-5 h-5 rounded bg-[var(--color-surface-raised)] flex items-center justify-center text-[10px] flex-shrink-0">
1118
- {dojoSelectedToolkitInfo.name?.[0]?.toUpperCase() || "?"}
1119
- </div>
1120
- )}
1121
- <span className="flex-1 truncate">{dojoSelectedToolkitInfo.name}</span>
1122
- <span className="text-[10px] text-[var(--color-text-muted)]">{dojoSelectedToolkitInfo.count} triggers</span>
1123
- </>
1124
- ) : (
1125
- <span className="text-[var(--color-text-muted)] flex-1">Select app...</span>
1126
- )}
1127
- <span className="text-[var(--color-text-muted)] text-xs ml-1">&#9662;</span>
1128
- </button>
1129
- {dojoAppDropdownOpen && (
1130
- <>
1131
- <div className="fixed inset-0 z-10" onClick={() => setDojoAppDropdownOpen(false)} />
1132
- <div className="absolute left-0 right-0 top-full mt-1 bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg shadow-xl z-20 max-h-64 flex flex-col">
1133
- <div className="p-2 border-b border-[var(--color-border)] flex-shrink-0">
1134
- <input
1135
- type="text"
1136
- value={dojoAppSearch}
1137
- onChange={(e) => setDojoAppSearch(e.target.value)}
1138
- placeholder="Search apps..."
1139
- className="w-full bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[var(--color-accent)]"
1140
- autoFocus
1141
- />
1142
- </div>
1143
- <div className="overflow-y-auto flex-1">
1144
- {dojoToolkits
1145
- .filter(tk => {
1146
- if (!dojoAppSearch) return true;
1147
- const s = dojoAppSearch.toLowerCase();
1148
- return tk.name.toLowerCase().includes(s) || tk.slug.toLowerCase().includes(s);
1149
- })
1150
- .map(tk => (
1151
- <button
1152
- key={tk.slug}
1153
- onClick={() => {
1154
- setDojoSelectedToolkit(tk.slug);
1155
- setDojoSelectedType("");
1156
- setDojoConfig({});
1157
- setDojoAppDropdownOpen(false);
1158
- }}
1159
- className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition hover:bg-[var(--color-surface-raised)] ${
1160
- dojoSelectedToolkit === tk.slug ? "bg-[var(--color-surface-raised)] text-[var(--color-accent)]" : ""
1161
- }`}
1162
- >
1163
- {tk.logo ? (
1164
- <img src={tk.logo} alt="" className="w-5 h-5 rounded object-contain flex-shrink-0" />
1165
- ) : (
1166
- <div className="w-5 h-5 rounded bg-[var(--color-surface-raised)] flex items-center justify-center text-[10px] flex-shrink-0">
1167
- {tk.name?.[0]?.toUpperCase() || "?"}
1168
- </div>
1169
- )}
1170
- <span className="flex-1 truncate">{tk.name}</span>
1171
- <span className="text-[10px] text-[var(--color-text-muted)]">{tk.count}</span>
1172
- </button>
1173
- ))}
1174
- </div>
1175
- </div>
1176
- </>
1177
- )}
1178
- </div>
1179
- </div>
1180
-
1181
- {/* Trigger selector — only shown when app is selected */}
1182
- {dojoSelectedToolkit && (
1183
- <div>
1184
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Trigger</label>
1185
- <div className="relative">
1186
- <button
1187
- onClick={() => { setDojoTriggerDropdownOpen(!dojoTriggerDropdownOpen); setDojoAppDropdownOpen(false); setDojoTriggerSearch(""); }}
1188
- className="w-full flex items-center gap-2 bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm text-left hover:border-[var(--color-text-faint)] transition"
1189
- >
1190
- {dojoSelectedTriggerType ? (
1191
- <>
1192
- <span className="flex-1 truncate">{dojoSelectedTriggerType.name}</span>
1193
- <span className={`text-[10px] px-1.5 py-0.5 rounded flex-shrink-0 ${
1194
- dojoSelectedTriggerType.type === "webhook" ? "bg-blue-500/10 text-blue-400" : "bg-yellow-500/10 text-yellow-400"
1195
- }`}>
1196
- {dojoSelectedTriggerType.type}
1197
- </span>
1198
- </>
1199
- ) : (
1200
- <span className="text-[var(--color-text-muted)] flex-1">Select trigger...</span>
1201
- )}
1202
- <span className="text-[var(--color-text-muted)] text-xs ml-1">&#9662;</span>
1203
- </button>
1204
- {dojoTriggerDropdownOpen && (
1205
- <>
1206
- <div className="fixed inset-0 z-10" onClick={() => setDojoTriggerDropdownOpen(false)} />
1207
- <div className="absolute left-0 right-0 top-full mt-1 bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg shadow-xl z-20 max-h-64 flex flex-col">
1208
- {dojoToolkitTriggers.length > 3 && (
1209
- <div className="p-2 border-b border-[var(--color-border)] flex-shrink-0">
1210
- <input
1211
- type="text"
1212
- value={dojoTriggerSearch}
1213
- onChange={(e) => setDojoTriggerSearch(e.target.value)}
1214
- placeholder="Search triggers..."
1215
- className="w-full bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[var(--color-accent)]"
1216
- autoFocus
1217
- />
1218
- </div>
1219
- )}
1220
- <div className="overflow-y-auto flex-1">
1221
- {dojoToolkitTriggers
1222
- .filter(t => {
1223
- if (!dojoTriggerSearch) return true;
1224
- const s = dojoTriggerSearch.toLowerCase();
1225
- return t.name.toLowerCase().includes(s) || t.slug.toLowerCase().includes(s) || t.description.toLowerCase().includes(s);
1226
- })
1227
- .map(t => (
1228
- <button
1229
- key={t.slug}
1230
- onClick={() => {
1231
- setDojoSelectedType(t.slug);
1232
- setDojoConfig({});
1233
- setDojoTriggerDropdownOpen(false);
1234
- }}
1235
- className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition hover:bg-[var(--color-surface-raised)] ${
1236
- dojoSelectedType === t.slug ? "bg-[var(--color-surface-raised)] text-[var(--color-accent)]" : ""
1237
- }`}
1238
- >
1239
- <div className="flex-1 min-w-0">
1240
- <div className="truncate">{t.name}</div>
1241
- <div className="text-[10px] text-[var(--color-text-muted)] truncate">{t.description}</div>
1242
- </div>
1243
- <span className={`text-[10px] px-1.5 py-0.5 rounded flex-shrink-0 ${
1244
- t.type === "webhook" ? "bg-blue-500/10 text-blue-400" : "bg-yellow-500/10 text-yellow-400"
1245
- }`}>
1246
- {t.type}
1247
- </span>
1248
- </button>
1249
- ))}
1250
- </div>
1251
- </div>
1252
- </>
1253
- )}
1254
- </div>
1255
- </div>
1256
- )}
1257
-
1258
- {/* Connected account — auto-matched */}
1259
- {dojoSelectedType && (
1260
- <div>
1261
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Connected Account</label>
1262
- {dojoMatchedAccount ? (
1263
- <div className="text-xs text-green-400 bg-green-500/10 border border-green-500/20 rounded p-3">
1264
- Connected: {dojoMatchedAccount.appName}
1265
- </div>
1266
- ) : (
1267
- <div className="text-xs text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded p-3">
1268
- No connected account for {dojoSelectedTriggerType?.toolkit_name || "this app"}. Connect it first in the Integrations tab.
1269
- </div>
1270
- )}
1271
- </div>
1272
- )}
1273
-
1274
- {/* Dynamic config fields from config_schema */}
1275
- {dojoSelectedTriggerType && dojoSelectedTriggerType.config_schema && Object.keys(dojoSelectedTriggerType.config_schema.properties || {}).length > 0 && (
1276
- <div>
1277
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Configuration</label>
1278
- <div className="space-y-2">
1279
- {Object.entries((dojoSelectedTriggerType.config_schema as any).properties || {}).map(([key, schema]: [string, any]) => {
1280
- const required = ((dojoSelectedTriggerType.config_schema as any).required || []).includes(key);
1281
- return (
1282
- <div key={key}>
1283
- <label className="block text-[11px] text-[var(--color-text-secondary)] mb-1">
1284
- {schema.title || key}
1285
- {required && <span className="text-red-400 ml-0.5">*</span>}
1286
- </label>
1287
- <input
1288
- type="text"
1289
- value={dojoConfig[key] || ""}
1290
- onChange={(e) => setDojoConfig(prev => ({ ...prev, [key]: e.target.value }))}
1291
- placeholder={schema.description || `Enter ${schema.title || key}...`}
1292
- className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
1293
- />
1294
- </div>
1295
- );
1296
- })}
1297
- </div>
1298
- </div>
1299
- )}
1300
-
1301
- {/* Agent selection */}
1302
- <div>
1303
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1.5">Target Agent</label>
1304
- {agents.length === 0 ? (
1305
- <div className="text-xs text-[var(--color-text-muted)] bg-[var(--color-bg)] rounded p-3">
1306
- No agents available. Create an agent first.
1307
- </div>
1308
- ) : (
1309
- <Select
1310
- value={dojoAgentId}
1311
- onChange={setDojoAgentId}
1312
- placeholder="Select agent..."
1313
- options={agents.map(agent => ({
1314
- value: agent.id,
1315
- label: `${agent.name} (${agent.status})`,
1316
- }))}
1317
- />
1318
- )}
1319
- </div>
1320
- </div>
1321
- )}
1322
-
1323
- <div className="flex gap-2 mt-5">
1324
- <button
1325
- onClick={() => setShowAddDojo(false)}
1326
- className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
1327
- >
1328
- Cancel
1329
- </button>
1330
- <button
1331
- onClick={handleAddDojoSub}
1332
- disabled={!dojoSelectedType || !dojoAgentId || !dojoMatchedAccount || dojoCreating || (
1333
- dojoSelectedTriggerType?.config_schema &&
1334
- ((dojoSelectedTriggerType.config_schema as any).required || []).some((key: string) => !dojoConfig[key]?.trim())
1335
- )}
1336
- className="flex-1 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition disabled:opacity-50"
1337
- >
1338
- {dojoCreating ? "Creating..." : "Subscribe"}
1339
- </button>
1340
- </div>
1341
- </div>
1342
- </div>
1343
- )}
1344
- </div>
1345
- );
1346
- }