apteva 0.4.18 → 0.4.19

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 (76) hide show
  1. package/dist/ActivityPage.9a1qg4bp.js +3 -0
  2. package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
  3. package/dist/App.1nmg2h01.js +4 -0
  4. package/dist/App.5qw2dtxs.js +4 -0
  5. package/dist/App.6nc5acvk.js +4 -0
  6. package/dist/{App.nps62kvt.js → App.7vzbaz56.js} +3 -3
  7. package/dist/App.8rfz30p1.js +4 -0
  8. package/dist/App.amwp54wf.js +4 -0
  9. package/dist/App.e4202qb4.js +267 -0
  10. package/dist/App.errxz2q4.js +4 -0
  11. package/dist/App.f8qsyhpr.js +4 -0
  12. package/dist/App.g8vq68n0.js +20 -0
  13. package/dist/App.kfyrnznw.js +13 -0
  14. package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
  15. package/dist/{App.np463xvy.js → App.p93mmyqw.js} +3 -3
  16. package/dist/App.qmg33p02.js +4 -0
  17. package/dist/{App.nft7h9jt.js → App.sdsc0258.js} +3 -3
  18. package/dist/ConnectionsPage.7zqba1r0.js +3 -0
  19. package/dist/McpPage.kf2g327t.js +3 -0
  20. package/dist/SettingsPage.472c15ep.js +3 -0
  21. package/dist/SkillsPage.xdxnh68a.js +3 -0
  22. package/dist/TasksPage.7g0b8xwc.js +3 -0
  23. package/dist/TelemetryPage.pr7rbz4r.js +3 -0
  24. package/dist/TestsPage.zhc6rqjm.js +3 -0
  25. package/dist/apteva-kit.css +1 -1
  26. package/dist/index.html +1 -1
  27. package/dist/styles.css +1 -1
  28. package/package.json +9 -4
  29. package/src/channels/index.ts +40 -0
  30. package/src/channels/telegram.ts +306 -0
  31. package/src/db.ts +180 -0
  32. package/src/integrations/agentdojo.ts +1 -1
  33. package/src/mcp-handler.ts +31 -24
  34. package/src/providers.ts +22 -9
  35. package/src/routes/api/channels.ts +182 -0
  36. package/src/routes/api/integrations.ts +13 -5
  37. package/src/routes/api/mcp.ts +27 -9
  38. package/src/routes/api/telemetry.ts +30 -0
  39. package/src/routes/api/triggers.ts +22 -2
  40. package/src/routes/api.ts +3 -1
  41. package/src/server.ts +39 -4
  42. package/src/triggers/agentdojo.ts +23 -18
  43. package/src/tui/AgentList.tsx +145 -0
  44. package/src/tui/App.tsx +102 -0
  45. package/src/tui/Login.tsx +104 -0
  46. package/src/tui/api.ts +72 -0
  47. package/src/tui/index.tsx +7 -0
  48. package/src/web/App.tsx +1 -1
  49. package/src/web/components/agents/AgentPanel.tsx +4 -37
  50. package/src/web/components/common/Icons.tsx +8 -0
  51. package/src/web/components/connections/OverviewTab.tsx +22 -68
  52. package/src/web/components/connections/TriggersTab.tsx +549 -70
  53. package/src/web/components/layout/Header.tsx +196 -4
  54. package/src/web/components/settings/SettingsPage.tsx +269 -1
  55. package/src/web/context/TelemetryContext.tsx +14 -1
  56. package/src/web/context/index.ts +1 -1
  57. package/dist/ActivityPage.yv28a2vj.js +0 -3
  58. package/dist/ApiDocsPage.4ccwjjbk.js +0 -4
  59. package/dist/App.155wke5v.js +0 -4
  60. package/dist/App.2e19nvn4.js +0 -13
  61. package/dist/App.2ye1b5n0.js +0 -4
  62. package/dist/App.4da4ycbe.js +0 -4
  63. package/dist/App.b6wtzd1j.js +0 -4
  64. package/dist/App.fjrh28tf.js +0 -4
  65. package/dist/App.htc36cy8.js +0 -4
  66. package/dist/App.me6reaa6.js +0 -4
  67. package/dist/App.n5q6p960.js +0 -4
  68. package/dist/App.q8ws33cc.js +0 -181
  69. package/dist/App.tb0y0jmt.js +0 -40
  70. package/dist/ConnectionsPage.52evzrp7.js +0 -3
  71. package/dist/McpPage.bjqrp0n2.js +0 -3
  72. package/dist/SettingsPage.es76hnj2.js +0 -3
  73. package/dist/SkillsPage.06h8yf0h.js +0 -3
  74. package/dist/TasksPage.99df66mk.js +0 -3
  75. package/dist/TelemetryPage.bmdnxhq7.js +0 -3
  76. package/dist/TestsPage.denxrg8c.js +0 -3
@@ -81,6 +81,19 @@ export function TriggersTab() {
81
81
  const [connectedAccounts, setConnectedAccounts] = useState<ConnectedAccount[]>([]);
82
82
  const [selectedAccountId, setSelectedAccountId] = useState("");
83
83
  const [creating, setCreating] = useState(false);
84
+ const [createAgentId, setCreateAgentId] = useState(""); // For AgentDojo direct subscription flow
85
+ const [browseConfig, setBrowseConfig] = useState<Record<string, string>>({});
86
+
87
+ // AgentDojo add subscription modal — uses composio as data source (same as Integrations tab)
88
+ const [showAddDojo, setShowAddDojo] = useState(false);
89
+ const [dojoTriggerTypes, setDojoTriggerTypes] = useState<TriggerType[]>([]);
90
+ const [dojoTypesLoading, setDojoTypesLoading] = useState(false);
91
+ const [dojoAccounts, setDojoAccounts] = useState<ConnectedAccount[]>([]);
92
+ const [dojoSelectedType, setDojoSelectedType] = useState<string>("");
93
+ const [dojoAgentId, setDojoAgentId] = useState("");
94
+ const [dojoCreating, setDojoCreating] = useState(false);
95
+ const [dojoTypeSearch, setDojoTypeSearch] = useState("");
96
+ const [dojoConfig, setDojoConfig] = useState<Record<string, string>>({});
84
97
 
85
98
  // Add subscription
86
99
  const [showAddSub, setShowAddSub] = useState(false);
@@ -98,7 +111,7 @@ export function TriggersTab() {
98
111
  // Fetch available providers
99
112
  const fetchProviders = useCallback(async () => {
100
113
  try {
101
- const res = await authFetch(`/api/triggers/providers`);
114
+ const res = await authFetch(`/api/triggers/providers${projectParam}`);
102
115
  if (res.ok) {
103
116
  const data = await res.json();
104
117
  setProviders(data.providers || []);
@@ -199,19 +212,169 @@ export function TriggersTab() {
199
212
  const startCreate = (triggerType: TriggerType) => {
200
213
  setSelectedType(triggerType);
201
214
  setSelectedAccountId("");
215
+ setCreateAgentId("");
216
+ setBrowseConfig({});
202
217
  setShowCreate(true);
203
218
  fetchConnectedAccounts();
204
219
  };
205
220
 
206
- // Create trigger
221
+ const isAgentDojo = selectedProvider === "agentdojo";
222
+
223
+ // Open AgentDojo add subscription modal — fetches from agentdojo provider (same as Integrations tab)
224
+ const openAddDojoSub = async () => {
225
+ setShowAddDojo(true);
226
+ setDojoSelectedType("");
227
+ setDojoAgentId("");
228
+ setDojoTypeSearch("");
229
+ setDojoConfig({});
230
+
231
+ console.log("[openAddDojoSub] Opening modal, selectedProvider:", selectedProvider);
232
+ console.log("[openAddDojoSub] currentProjectId:", currentProjectId, "projectParam:", projectParam);
233
+ console.log("[openAddDojoSub] existing dojoTriggerTypes:", dojoTriggerTypes.length, "dojoAccounts:", dojoAccounts.length);
234
+
235
+ // Fetch trigger types from agentdojo (same provider as Integrations tab uses)
236
+ const loadTypes = async () => {
237
+ if (dojoTriggerTypes.length > 0) {
238
+ console.log("[openAddDojoSub] Already have", dojoTriggerTypes.length, "trigger types, skipping fetch");
239
+ return;
240
+ }
241
+ setDojoTypesLoading(true);
242
+ try {
243
+ let url = `/api/triggers/types?provider=agentdojo`;
244
+ if (currentProjectId && currentProjectId !== "unassigned") url += `&project_id=${currentProjectId}`;
245
+ console.log("[openAddDojoSub] Fetching trigger types:", url);
246
+ const res = await authFetch(url);
247
+ const data = await res.json();
248
+ console.log("[openAddDojoSub] Trigger types response:", res.status, "ok:", res.ok, "types count:", (data.types || []).length, "error:", data.error);
249
+ if (data.types && data.types.length > 0) {
250
+ console.log("[openAddDojoSub] Sample trigger type:", JSON.stringify(data.types[0]));
251
+ }
252
+ setDojoTriggerTypes(data.types || []);
253
+ } catch (e) {
254
+ console.error("[openAddDojoSub] Failed to load trigger types:", e);
255
+ }
256
+ setDojoTypesLoading(false);
257
+ };
258
+
259
+ // Fetch connected accounts from agentdojo (same as Integrations tab)
260
+ const loadAccounts = async () => {
261
+ try {
262
+ const url = `/api/integrations/agentdojo/connected${projectParam}`;
263
+ console.log("[openAddDojoSub] Fetching connected accounts:", url);
264
+ const res = await authFetch(url);
265
+ const data = await res.json();
266
+ console.log("[openAddDojoSub] Connected accounts response:", res.status, "ok:", res.ok, "accounts count:", (data.accounts || []).length, "error:", data.error);
267
+ if (data.accounts && data.accounts.length > 0) {
268
+ console.log("[openAddDojoSub] Sample account:", JSON.stringify(data.accounts[0]));
269
+ }
270
+ const active = (data.accounts || []).filter((a: ConnectedAccount) => a.status === "active");
271
+ console.log("[openAddDojoSub] Active accounts:", active.length);
272
+ setDojoAccounts(active);
273
+ } catch (e) {
274
+ console.error("[openAddDojoSub] Failed to load connected accounts:", e);
275
+ }
276
+ };
277
+
278
+ await Promise.all([loadTypes(), loadAccounts()]);
279
+ };
280
+
281
+ // Create AgentDojo subscription from the add-subscription modal
282
+ const handleAddDojoSub = async () => {
283
+ const tt = dojoTriggerTypes.find(t => t.slug === dojoSelectedType);
284
+ const matched = tt ? matchAccount(dojoAccounts, tt.toolkit_slug) : null;
285
+ console.log("[handleAddDojoSub] tt:", tt?.slug, "matched:", matched?.id, matched?.appName, "agentId:", dojoAgentId);
286
+ if (!tt || !dojoAgentId || !matched) return;
287
+
288
+ setDojoCreating(true);
289
+ setError(null);
290
+ try {
291
+ const agent = agents.find(a => a.id === dojoAgentId);
292
+ const providerParam = `provider=agentdojo`;
293
+ const url = projectParam
294
+ ? `/api/triggers${projectParam}&${providerParam}`
295
+ : `/api/triggers?${providerParam}`;
296
+ const configPayload = {
297
+ callback_url: `${window.location.origin}/api/webhooks/agentdojo`,
298
+ title: `${tt.name} → ${agent?.name || "Agent"}`,
299
+ server: tt.toolkit_slug,
300
+ agent_id: dojoAgentId,
301
+ ...dojoConfig, // Merge dynamic config fields (e.g. owner, repo for GitHub)
302
+ };
303
+ console.log("[handleAddDojoSub] config:", JSON.stringify(configPayload));
304
+ const res = await authFetch(url, {
305
+ method: "POST",
306
+ headers: { "Content-Type": "application/json" },
307
+ body: JSON.stringify({
308
+ slug: tt.slug,
309
+ connectedAccountId: matched.id,
310
+ config: configPayload,
311
+ }),
312
+ });
313
+ const data = await res.json();
314
+ if (!res.ok) {
315
+ setError(data.error || "Failed to create subscription");
316
+ } else {
317
+ setShowAddDojo(false);
318
+ fetchTriggers();
319
+ }
320
+ } catch (e: any) {
321
+ setError(e.message || "Failed to create subscription");
322
+ }
323
+ setDojoCreating(false);
324
+ };
325
+
326
+ // Create trigger (Composio: trigger instance, AgentDojo: subscription + agent routing)
207
327
  const handleCreate = async () => {
208
- if (!selectedType || !selectedAccountId) return;
328
+ if (!selectedType) return;
209
329
 
330
+ // AgentDojo: create remote subscription directly (callback_url points to apteva webhook handler)
331
+ if (isAgentDojo) {
332
+ if (!createAgentId || !browseMatchedAccount) return;
333
+ setCreating(true);
334
+ setError(null);
335
+ try {
336
+ const agent = agents.find(a => a.id === createAgentId);
337
+ const instanceUrl = window.location.origin;
338
+ const providerParam = `provider=${selectedProvider}`;
339
+ const url = projectParam
340
+ ? `/api/triggers${projectParam}&${providerParam}`
341
+ : `/api/triggers?${providerParam}`;
342
+ const res = await authFetch(url, {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json" },
345
+ body: JSON.stringify({
346
+ slug: selectedType.slug,
347
+ connectedAccountId: browseMatchedAccount.id,
348
+ config: {
349
+ callback_url: `${instanceUrl}/api/webhooks/agentdojo`,
350
+ title: `${selectedType.name} → ${agent?.name || "Agent"}`,
351
+ server: selectedType.toolkit_slug,
352
+ agent_id: createAgentId,
353
+ ...browseConfig, // Dynamic config fields (e.g. owner, repo)
354
+ },
355
+ }),
356
+ });
357
+ const data = await res.json();
358
+ if (!res.ok) {
359
+ setError(data.error || "Failed to create subscription");
360
+ } else {
361
+ setShowCreate(false);
362
+ setSelectedType(null);
363
+ fetchTriggers();
364
+ }
365
+ } catch (e: any) {
366
+ setError(e.message || "Failed to create subscription");
367
+ }
368
+ setCreating(false);
369
+ return;
370
+ }
371
+
372
+ // Composio: standard trigger instance creation
373
+ if (!selectedAccountId) return;
210
374
  setCreating(true);
211
375
  setError(null);
212
376
  try {
213
377
  const providerParam = `provider=${selectedProvider}`;
214
- const sep = projectParam ? "&" : "?";
215
378
  const url = projectParam
216
379
  ? `/api/triggers${projectParam}&${providerParam}`
217
380
  : `/api/triggers?${providerParam}`;
@@ -344,6 +507,25 @@ export function TriggersTab() {
344
507
  return t.name.toLowerCase().includes(s) || t.slug.toLowerCase().includes(s) || t.description.toLowerCase().includes(s);
345
508
  });
346
509
 
510
+ // Auto-match connected account from toolkit slug
511
+ const matchAccount = (accounts: ConnectedAccount[], toolkitSlug: string): ConnectedAccount | null => {
512
+ if (!toolkitSlug || accounts.length === 0) return null;
513
+ const slug = toolkitSlug.toLowerCase();
514
+ return accounts.find(a =>
515
+ a.appId?.toLowerCase() === slug ||
516
+ a.appName?.toLowerCase() === slug ||
517
+ a.appId?.toLowerCase().includes(slug) ||
518
+ a.appName?.toLowerCase().includes(slug)
519
+ ) || null;
520
+ };
521
+
522
+ // Derived: auto-matched account for Add Subscription modal
523
+ const dojoSelectedTriggerType = dojoTriggerTypes.find(t => t.slug === dojoSelectedType);
524
+ const dojoMatchedAccount = dojoSelectedTriggerType ? matchAccount(dojoAccounts, dojoSelectedTriggerType.toolkit_slug) : null;
525
+
526
+ // Derived: auto-matched account for Browse Subscribe modal
527
+ const browseMatchedAccount = selectedType && isAgentDojo ? matchAccount(connectedAccounts, selectedType.toolkit_slug) : null;
528
+
347
529
  // Agent map for quick lookups
348
530
  const agentMap = new Map(agents.map(a => [a.id, a]));
349
531
 
@@ -387,7 +569,8 @@ export function TriggersTab() {
387
569
  </div>
388
570
  )}
389
571
 
390
- {/* Subscriptions (trigger → agent routing) */}
572
+ {/* Subscriptions (trigger → agent routing) — hide entirely for AgentDojo (handled in Active Subscriptions) */}
573
+ {!isAgentDojo && (
391
574
  <section>
392
575
  <div className="flex items-center justify-between mb-3">
393
576
  <h3 className="text-sm font-medium text-[#888]">
@@ -449,54 +632,126 @@ export function TriggersTab() {
449
632
  </div>
450
633
  )}
451
634
  </section>
635
+ )}
452
636
 
453
- {/* Trigger Instances */}
454
- <section>
455
- <h3 className="text-sm font-medium text-[#888] mb-3">
456
- Trigger Instances ({triggers.length})
457
- </h3>
458
- {triggersLoading ? (
459
- <div className="text-center py-6 text-[#666] text-sm">Loading triggers...</div>
460
- ) : triggers.length === 0 ? (
461
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6 text-center text-[#666] text-sm">
462
- No trigger instances. Browse trigger types below to create one.
463
- </div>
464
- ) : (
465
- <div className="space-y-2">
466
- {triggers.map(trigger => (
467
- <div key={trigger.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 flex items-center gap-3">
468
- <div className={`w-2 h-2 rounded-full flex-shrink-0 ${trigger.status === "active" ? "bg-green-400" : "bg-[#666]"}`} />
469
- <div className="flex-1 min-w-0">
470
- <div className="text-sm font-medium truncate">
471
- {trigger.trigger_slug.replace(/_/g, " ")}
637
+ {/* Trigger Instances — only show for providers that have them (not AgentDojo) */}
638
+ {!isAgentDojo && (
639
+ <section>
640
+ <h3 className="text-sm font-medium text-[#888] mb-3">
641
+ Trigger Instances ({triggers.length})
642
+ </h3>
643
+ {triggersLoading ? (
644
+ <div className="text-center py-6 text-[#666] text-sm">Loading triggers...</div>
645
+ ) : triggers.length === 0 ? (
646
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6 text-center text-[#666] text-sm">
647
+ No trigger instances. Browse trigger types below to create one.
648
+ </div>
649
+ ) : (
650
+ <div className="space-y-2">
651
+ {triggers.map(trigger => (
652
+ <div key={trigger.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 flex items-center gap-3">
653
+ <div className={`w-2 h-2 rounded-full flex-shrink-0 ${trigger.status === "active" ? "bg-green-400" : "bg-[#666]"}`} />
654
+ <div className="flex-1 min-w-0">
655
+ <div className="text-sm font-medium truncate">
656
+ {trigger.trigger_slug.replace(/_/g, " ")}
657
+ </div>
658
+ <div className="text-xs text-[#666]">
659
+ ID: {trigger.id.slice(0, 12)}... | Created: {new Date(trigger.created_at).toLocaleDateString()}
660
+ </div>
472
661
  </div>
473
- <div className="text-xs text-[#666]">
474
- ID: {trigger.id.slice(0, 12)}... | Created: {new Date(trigger.created_at).toLocaleDateString()}
662
+ <div className="flex items-center gap-2 flex-shrink-0">
663
+ <button
664
+ onClick={() => toggleTrigger(trigger.id, trigger.status)}
665
+ className={`text-xs px-3 py-1 rounded transition ${
666
+ trigger.status === "active"
667
+ ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
668
+ : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
669
+ }`}
670
+ >
671
+ {trigger.status === "active" ? "Disable" : "Enable"}
672
+ </button>
673
+ <button
674
+ onClick={() => deleteTrigger(trigger.id)}
675
+ className="text-xs text-[#666] hover:text-red-400 transition px-2"
676
+ >
677
+ Delete
678
+ </button>
475
679
  </div>
476
680
  </div>
477
- <div className="flex items-center gap-2 flex-shrink-0">
478
- <button
479
- onClick={() => toggleTrigger(trigger.id, trigger.status)}
480
- className={`text-xs px-3 py-1 rounded transition ${
481
- trigger.status === "active"
482
- ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
483
- : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
484
- }`}
485
- >
486
- {trigger.status === "active" ? "Disable" : "Enable"}
487
- </button>
488
- <button
489
- onClick={() => deleteTrigger(trigger.id)}
490
- className="text-xs text-[#666] hover:text-red-400 transition px-2"
491
- >
492
- Delete
493
- </button>
494
- </div>
495
- </div>
496
- ))}
681
+ ))}
682
+ </div>
683
+ )}
684
+ </section>
685
+ )}
686
+
687
+ {/* AgentDojo Active Subscriptions — shows remote subscriptions directly */}
688
+ {isAgentDojo && (
689
+ <section>
690
+ <div className="flex items-center justify-between mb-3">
691
+ <h3 className="text-sm font-medium text-[#888]">
692
+ Active Subscriptions ({triggers.length})
693
+ </h3>
694
+ <button
695
+ onClick={openAddDojoSub}
696
+ className="text-xs bg-[#1a1a1a] hover:bg-[#222] border border-[#333] hover:border-[#f97316] px-3 py-1.5 rounded transition"
697
+ >
698
+ + Add Subscription
699
+ </button>
497
700
  </div>
498
- )}
499
- </section>
701
+ {triggersLoading ? (
702
+ <div className="text-center py-6 text-[#666] text-sm">Loading subscriptions...</div>
703
+ ) : triggers.length === 0 ? (
704
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6 text-center text-[#666] text-sm">
705
+ No active subscriptions. Browse trigger types below to create one.
706
+ </div>
707
+ ) : (
708
+ <div className="space-y-2">
709
+ {triggers.map(trigger => {
710
+ const localSub = subscriptions.find(s => s.trigger_instance_id === trigger.id);
711
+ const agent = localSub ? agentMap.get(localSub.agent_id) : null;
712
+ return (
713
+ <div key={trigger.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 flex items-center gap-3">
714
+ <div className={`w-2 h-2 rounded-full flex-shrink-0 ${trigger.status === "active" ? "bg-green-400" : "bg-[#666]"}`} />
715
+ <div className="flex-1 min-w-0">
716
+ <div className="text-sm font-medium truncate">
717
+ {(trigger.config?.title as string) || trigger.trigger_slug.replace(/_/g, " ")}
718
+ {agent && (
719
+ <>
720
+ <span className="text-[#555] mx-1.5">&rarr;</span>
721
+ <span className="text-[#f97316]">{agent.name}</span>
722
+ </>
723
+ )}
724
+ </div>
725
+ <div className="text-xs text-[#666]">
726
+ {trigger.config?.server && <span>{String(trigger.config.server)} | </span>}
727
+ ID: {String(trigger.id).slice(0, 8)} | Created: {new Date(trigger.created_at).toLocaleDateString()}
728
+ </div>
729
+ </div>
730
+ <div className="flex items-center gap-2 flex-shrink-0">
731
+ <button
732
+ onClick={() => toggleTrigger(trigger.id, trigger.status)}
733
+ className={`text-xs px-3 py-1 rounded transition ${
734
+ trigger.status === "active"
735
+ ? "bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
736
+ : "bg-green-500/10 text-green-400 hover:bg-green-500/20"
737
+ }`}
738
+ >
739
+ {trigger.status === "active" ? "Disable" : "Enable"}
740
+ </button>
741
+ <button
742
+ onClick={() => deleteTrigger(trigger.id)}
743
+ className="text-xs text-[#666] hover:text-red-400 transition px-2"
744
+ >
745
+ Delete
746
+ </button>
747
+ </div>
748
+ </div>
749
+ );
750
+ })}
751
+ </div>
752
+ )}
753
+ </section>
754
+ )}
500
755
 
501
756
  {/* Browse Trigger Types */}
502
757
  <section>
@@ -548,7 +803,7 @@ export function TriggersTab() {
548
803
  onClick={() => startCreate(tt)}
549
804
  className="w-full mt-3 text-xs bg-[#1a1a1a] hover:bg-[#222] border border-[#333] hover:border-[#f97316] px-3 py-1.5 rounded transition"
550
805
  >
551
- Create Trigger
806
+ {isAgentDojo ? "Subscribe" : "Create Trigger"}
552
807
  </button>
553
808
  </div>
554
809
  ))}
@@ -566,28 +821,99 @@ export function TriggersTab() {
566
821
  {showCreate && selectedType && (
567
822
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
568
823
  <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-md mx-4">
569
- <h3 className="font-medium mb-1">Create Trigger</h3>
570
- <p className="text-xs text-[#666] mb-4">{selectedType.name}</p>
824
+ <h3 className="font-medium mb-1">
825
+ {isAgentDojo ? "Create Subscription" : "Create Trigger"}
826
+ </h3>
827
+ <p className="text-xs text-[#666] mb-4">
828
+ {selectedType.name}
829
+ {selectedType.toolkit_name && <span className="text-[#555]"> ({selectedType.toolkit_name})</span>}
830
+ </p>
571
831
 
572
832
  <div className="space-y-4">
573
- <div>
574
- <label className="block text-xs text-[#888] mb-1.5">Connected Account</label>
575
- {connectedAccounts.length === 0 ? (
576
- <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">
577
- No connected accounts available. Connect an app first in the Integrations tab.
833
+ {/* Connected Account — only for Composio */}
834
+ {!isAgentDojo && (
835
+ <div>
836
+ <label className="block text-xs text-[#888] mb-1.5">Connected Account</label>
837
+ {connectedAccounts.length === 0 ? (
838
+ <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">
839
+ No connected accounts available. Connect an app first in the Integrations tab.
840
+ </div>
841
+ ) : (
842
+ <Select
843
+ value={selectedAccountId}
844
+ onChange={setSelectedAccountId}
845
+ placeholder="Select account..."
846
+ options={connectedAccounts.map(acc => ({
847
+ value: acc.id,
848
+ label: `${acc.appName} (${acc.id.slice(0, 8)}...)`,
849
+ }))}
850
+ />
851
+ )}
852
+ </div>
853
+ )}
854
+
855
+ {/* Agent selection — for AgentDojo direct subscription */}
856
+ {isAgentDojo && (
857
+ <div>
858
+ <label className="block text-xs text-[#888] mb-1.5">Route to Agent</label>
859
+ {agents.length === 0 ? (
860
+ <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">
861
+ No agents available. Create an agent first.
862
+ </div>
863
+ ) : (
864
+ <Select
865
+ value={createAgentId}
866
+ onChange={setCreateAgentId}
867
+ placeholder="Select agent..."
868
+ options={agents.map(agent => ({
869
+ value: agent.id,
870
+ label: `${agent.name} (${agent.status})`,
871
+ }))}
872
+ />
873
+ )}
874
+
875
+ {/* Connected account — auto-matched from toolkit */}
876
+ <div className="mt-3">
877
+ <label className="block text-xs text-[#888] mb-1.5">Connected Account</label>
878
+ {browseMatchedAccount ? (
879
+ <div className="text-xs text-green-400 bg-green-500/10 border border-green-500/20 rounded p-3">
880
+ Connected: {browseMatchedAccount.appName}
881
+ </div>
882
+ ) : (
883
+ <div className="text-xs text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded p-3">
884
+ No connected account for {selectedType?.toolkit_name || "this app"}. Connect it first in the Integrations tab.
885
+ </div>
886
+ )}
578
887
  </div>
579
- ) : (
580
- <Select
581
- value={selectedAccountId}
582
- onChange={setSelectedAccountId}
583
- placeholder="Select account..."
584
- options={connectedAccounts.map(acc => ({
585
- value: acc.id,
586
- label: `${acc.appName} (${acc.id.slice(0, 8)}...)`,
587
- }))}
588
- />
589
- )}
590
- </div>
888
+
889
+ {/* Dynamic config fields from config_schema */}
890
+ {selectedType.config_schema && Object.keys((selectedType.config_schema as any).properties || {}).length > 0 && (
891
+ <div className="mt-3">
892
+ <label className="block text-xs text-[#888] mb-1.5">Configuration</label>
893
+ <div className="space-y-2">
894
+ {Object.entries((selectedType.config_schema as any).properties || {}).map(([key, schema]: [string, any]) => {
895
+ const required = ((selectedType.config_schema as any).required || []).includes(key);
896
+ return (
897
+ <div key={key}>
898
+ <label className="block text-[11px] text-[#888] mb-1">
899
+ {schema.title || key}
900
+ {required && <span className="text-red-400 ml-0.5">*</span>}
901
+ </label>
902
+ <input
903
+ type="text"
904
+ value={browseConfig[key] || ""}
905
+ onChange={(e) => setBrowseConfig(prev => ({ ...prev, [key]: e.target.value }))}
906
+ placeholder={schema.description || `Enter ${schema.title || key}...`}
907
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
908
+ />
909
+ </div>
910
+ );
911
+ })}
912
+ </div>
913
+ </div>
914
+ )}
915
+ </div>
916
+ )}
591
917
  </div>
592
918
 
593
919
  <div className="flex gap-2 mt-4">
@@ -599,10 +925,13 @@ export function TriggersTab() {
599
925
  </button>
600
926
  <button
601
927
  onClick={handleCreate}
602
- disabled={!selectedAccountId || creating}
928
+ disabled={isAgentDojo ? (
929
+ !createAgentId || !browseMatchedAccount || creating ||
930
+ (selectedType?.config_schema && ((selectedType.config_schema as any).required || []).some((key: string) => !browseConfig[key]?.trim()))
931
+ ) : (!selectedAccountId || creating)}
603
932
  className="flex-1 text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition disabled:opacity-50"
604
933
  >
605
- {creating ? "Creating..." : "Create"}
934
+ {creating ? "Creating..." : isAgentDojo ? "Subscribe" : "Create"}
606
935
  </button>
607
936
  </div>
608
937
  </div>
@@ -685,6 +1014,156 @@ export function TriggersTab() {
685
1014
  </div>
686
1015
  </div>
687
1016
  )}
1017
+
1018
+ {/* AgentDojo Add Subscription Modal */}
1019
+ {showAddDojo && (
1020
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1021
+ <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-lg mx-4">
1022
+ <h3 className="font-medium mb-1">Add Subscription</h3>
1023
+ <p className="text-xs text-[#666] mb-4">
1024
+ Select a trigger from your connected apps and route it to an agent.
1025
+ </p>
1026
+
1027
+ <div className="space-y-4">
1028
+ {/* Trigger type selection */}
1029
+ <div>
1030
+ <label className="block text-xs text-[#888] mb-1.5">Trigger</label>
1031
+ {dojoTypesLoading ? (
1032
+ <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">Loading triggers...</div>
1033
+ ) : dojoTriggerTypes.length === 0 ? (
1034
+ <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">
1035
+ No triggers available. Connect an app first in the Integrations tab.
1036
+ </div>
1037
+ ) : (
1038
+ <>
1039
+ <input
1040
+ type="text"
1041
+ value={dojoTypeSearch}
1042
+ onChange={(e) => setDojoTypeSearch(e.target.value)}
1043
+ placeholder="Search triggers..."
1044
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 text-sm mb-2 focus:outline-none focus:border-[#f97316]"
1045
+ />
1046
+ <div className="max-h-48 overflow-y-auto border border-[#1a1a1a] rounded-lg">
1047
+ {dojoTriggerTypes
1048
+ .filter(t => {
1049
+ if (!dojoTypeSearch) return true;
1050
+ const s = dojoTypeSearch.toLowerCase();
1051
+ return t.name.toLowerCase().includes(s) || t.slug.toLowerCase().includes(s) || t.toolkit_name.toLowerCase().includes(s);
1052
+ })
1053
+ .slice(0, 50)
1054
+ .map(t => (
1055
+ <button
1056
+ key={t.slug}
1057
+ onClick={() => { setDojoSelectedType(t.slug); setDojoConfig({}); }}
1058
+ className={`w-full text-left px-3 py-2 text-sm flex items-center gap-2 transition border-b border-[#1a1a1a] last:border-0 ${
1059
+ dojoSelectedType === t.slug
1060
+ ? "bg-[#f97316]/10 text-[#f97316]"
1061
+ : "hover:bg-[#1a1a1a] text-[#ccc]"
1062
+ }`}
1063
+ >
1064
+ {t.logo ? (
1065
+ <img src={t.logo} alt="" className="w-5 h-5 rounded object-contain flex-shrink-0" />
1066
+ ) : (
1067
+ <div className="w-5 h-5 rounded bg-[#1a1a1a] flex items-center justify-center text-[10px] flex-shrink-0">
1068
+ {t.toolkit_name?.[0]?.toUpperCase() || "?"}
1069
+ </div>
1070
+ )}
1071
+ <div className="flex-1 min-w-0">
1072
+ <div className="truncate">{t.name}</div>
1073
+ <div className="text-[10px] text-[#666] truncate">{t.toolkit_name}</div>
1074
+ </div>
1075
+ </button>
1076
+ ))}
1077
+ </div>
1078
+ </>
1079
+ )}
1080
+ </div>
1081
+
1082
+ {/* Connected account — auto-matched */}
1083
+ {dojoSelectedType && (
1084
+ <div>
1085
+ <label className="block text-xs text-[#888] mb-1.5">Connected Account</label>
1086
+ {dojoMatchedAccount ? (
1087
+ <div className="text-xs text-green-400 bg-green-500/10 border border-green-500/20 rounded p-3">
1088
+ Connected: {dojoMatchedAccount.appName}
1089
+ </div>
1090
+ ) : (
1091
+ <div className="text-xs text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded p-3">
1092
+ No connected account for {dojoSelectedTriggerType?.toolkit_name || "this app"}. Connect it first in the Integrations tab.
1093
+ </div>
1094
+ )}
1095
+ </div>
1096
+ )}
1097
+
1098
+ {/* Dynamic config fields from config_schema */}
1099
+ {dojoSelectedTriggerType && dojoSelectedTriggerType.config_schema && Object.keys(dojoSelectedTriggerType.config_schema.properties || {}).length > 0 && (
1100
+ <div>
1101
+ <label className="block text-xs text-[#888] mb-1.5">Configuration</label>
1102
+ <div className="space-y-2">
1103
+ {Object.entries((dojoSelectedTriggerType.config_schema as any).properties || {}).map(([key, schema]: [string, any]) => {
1104
+ const required = ((dojoSelectedTriggerType.config_schema as any).required || []).includes(key);
1105
+ return (
1106
+ <div key={key}>
1107
+ <label className="block text-[11px] text-[#888] mb-1">
1108
+ {schema.title || key}
1109
+ {required && <span className="text-red-400 ml-0.5">*</span>}
1110
+ </label>
1111
+ <input
1112
+ type="text"
1113
+ value={dojoConfig[key] || ""}
1114
+ onChange={(e) => setDojoConfig(prev => ({ ...prev, [key]: e.target.value }))}
1115
+ placeholder={schema.description || `Enter ${schema.title || key}...`}
1116
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
1117
+ />
1118
+ </div>
1119
+ );
1120
+ })}
1121
+ </div>
1122
+ </div>
1123
+ )}
1124
+
1125
+ {/* Agent selection */}
1126
+ <div>
1127
+ <label className="block text-xs text-[#888] mb-1.5">Target Agent</label>
1128
+ {agents.length === 0 ? (
1129
+ <div className="text-xs text-[#666] bg-[#0a0a0a] rounded p-3">
1130
+ No agents available. Create an agent first.
1131
+ </div>
1132
+ ) : (
1133
+ <Select
1134
+ value={dojoAgentId}
1135
+ onChange={setDojoAgentId}
1136
+ placeholder="Select agent..."
1137
+ options={agents.map(agent => ({
1138
+ value: agent.id,
1139
+ label: `${agent.name} (${agent.status})`,
1140
+ }))}
1141
+ />
1142
+ )}
1143
+ </div>
1144
+ </div>
1145
+
1146
+ <div className="flex gap-2 mt-5">
1147
+ <button
1148
+ onClick={() => setShowAddDojo(false)}
1149
+ className="flex-1 text-sm bg-[#1a1a1a] hover:bg-[#222] border border-[#333] px-4 py-2 rounded transition"
1150
+ >
1151
+ Cancel
1152
+ </button>
1153
+ <button
1154
+ onClick={handleAddDojoSub}
1155
+ disabled={!dojoSelectedType || !dojoAgentId || !dojoMatchedAccount || dojoCreating || (
1156
+ dojoSelectedTriggerType?.config_schema &&
1157
+ ((dojoSelectedTriggerType.config_schema as any).required || []).some((key: string) => !dojoConfig[key]?.trim())
1158
+ )}
1159
+ className="flex-1 text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition disabled:opacity-50"
1160
+ >
1161
+ {dojoCreating ? "Creating..." : "Subscribe"}
1162
+ </button>
1163
+ </div>
1164
+ </div>
1165
+ </div>
1166
+ )}
688
1167
  </div>
689
1168
  );
690
1169
  }