@greatapps/greatagents-ui 0.3.14 → 0.3.15

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/greatagents-ui",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "description": "Shared agents UI components for Great platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -63,29 +63,59 @@ function buildPreview(
63
63
  let preview = promptText;
64
64
 
65
65
  const activeObjectives = objectives.filter((o) => o.active);
66
- if (activeObjectives.length > 0) {
67
- preview += "\n\n[SKILLS DISPONÍVEIS]\n";
68
- for (const obj of activeObjectives) {
69
- preview += `- ${obj.title}`;
70
- if (obj.prompt) preview += `: ${obj.prompt}`;
71
- preview += "\n";
66
+ const enabledAgentTools = agentTools.filter((at) => at.enabled);
67
+
68
+ const toolMap = new Map(allTools.map((t) => [t.id, t]));
69
+
70
+ // Separate capabilities vs integrations
71
+ const capabilityTools: { at: AgentTool; tool: Tool }[] = [];
72
+ const integrationTools: { at: AgentTool; tool: Tool }[] = [];
73
+ for (const at of enabledAgentTools) {
74
+ const tool = toolMap.get(at.id_tool);
75
+ if (!tool) continue;
76
+ if (tool.type === "integration") {
77
+ integrationTools.push({ at, tool });
78
+ } else {
79
+ capabilityTools.push({ at, tool });
72
80
  }
73
81
  }
74
82
 
75
- const enabledAgentTools = agentTools.filter((at) => at.enabled);
76
- if (enabledAgentTools.length > 0) {
77
- const toolMap = new Map(allTools.map((t) => [t.id, t]));
78
- preview += "\n[TOOLS DISPONÍVEIS]\n";
79
- for (const at of enabledAgentTools) {
80
- const tool = toolMap.get(at.id_tool);
81
- const name = tool?.name || `Tool #${at.id_tool}`;
82
- const desc = tool?.description ? `: ${tool.description}` : "";
83
- preview += `- ${name}${desc}`;
84
- if (at.custom_instructions) {
85
- preview += `\n Instruções: ${at.custom_instructions}`;
83
+ const hasContent = activeObjectives.length > 0 || capabilityTools.length > 0 || integrationTools.length > 0;
84
+
85
+ if (hasContent) {
86
+ preview += "\n\n[CAPACIDADES E INTEGRAÇÕES]";
87
+
88
+ // Internal capabilities
89
+ if (activeObjectives.length > 0 || capabilityTools.length > 0) {
90
+ preview += "\n\n## Capacidades Internas (GClinic)";
91
+
92
+ for (const obj of activeObjectives) {
93
+ preview += `\n\n### ${obj.title} (${obj.slug})`;
94
+ if (obj.prompt) preview += `\n${obj.prompt}`;
95
+ }
96
+
97
+ for (const { at, tool } of capabilityTools) {
98
+ preview += `\n\n### ${tool.name} (${tool.slug})`;
99
+ if (tool.description) preview += `\n${tool.description}`;
100
+ if (at.custom_instructions) preview += `\n${at.custom_instructions}`;
86
101
  }
87
- preview += "\n";
88
102
  }
103
+
104
+ // External integrations
105
+ if (integrationTools.length > 0) {
106
+ preview += "\n\n## Integrações Externas";
107
+
108
+ for (const { at, tool } of integrationTools) {
109
+ preview += `\n\n### ${tool.name} (${tool.slug})`;
110
+ if (at.custom_instructions) preview += `\n${at.custom_instructions}`;
111
+ }
112
+ }
113
+
114
+ // Rules
115
+ preview += "\n\n## Regras";
116
+ preview += "\n- Sempre confirme com o usuário antes de criar ou alterar registros.";
117
+ preview += "\n- Nunca invente dados — sempre consulte primeiro.";
118
+ preview += "\n- Use EXATAMENTE os nomes de função listados acima.";
89
119
  }
90
120
 
91
121
  return preview;
@@ -277,13 +307,20 @@ export function AgentPromptEditor({ config, agent }: AgentPromptEditorProps) {
277
307
  <div className="border-t px-4 py-3">
278
308
  <pre className="max-h-96 overflow-auto whitespace-pre-wrap font-mono text-sm leading-relaxed">
279
309
  {previewText.split("\n").map((line, i) => {
280
- const isSection =
281
- line.startsWith("[SKILLS DISPONÍVEIS]") ||
282
- line.startsWith("[TOOLS DISPONÍVEIS]");
310
+ const isTopSection = line.startsWith("[CAPACIDADES E INTEGRAÇÕES]");
311
+ const isH2 = line.startsWith("## ");
312
+ const isH3 = line.startsWith("### ");
313
+ const cls = isTopSection
314
+ ? "font-bold text-foreground"
315
+ : isH2
316
+ ? "font-semibold text-muted-foreground"
317
+ : isH3
318
+ ? "font-medium text-muted-foreground"
319
+ : "";
283
320
  return (
284
321
  <span
285
322
  key={i}
286
- className={isSection ? "font-semibold text-muted-foreground" : ""}
323
+ className={cls}
287
324
  >
288
325
  {line}
289
326
  {"\n"}
@@ -1,7 +1,23 @@
1
1
  'use client';
2
2
 
3
+ import { useState } from "react";
3
4
  import type { IntegrationCardData, IntegrationCardState } from "../../hooks/use-integrations";
4
- import { Badge, Button } from "@greatapps/greatauth-ui/ui";
5
+ import {
6
+ Badge,
7
+ Button,
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuTrigger,
12
+ AlertDialog,
13
+ AlertDialogAction,
14
+ AlertDialogCancel,
15
+ AlertDialogContent,
16
+ AlertDialogDescription,
17
+ AlertDialogFooter,
18
+ AlertDialogHeader,
19
+ AlertDialogTitle,
20
+ } from "@greatapps/greatauth-ui/ui";
5
21
  import {
6
22
  CalendarSync,
7
23
  Plug,
@@ -9,6 +25,9 @@ import {
9
25
  RefreshCw,
10
26
  Clock,
11
27
  Plus,
28
+ MoreVertical,
29
+ Unplug,
30
+ Trash2,
12
31
  } from "lucide-react";
13
32
  import type { LucideIcon } from "lucide-react";
14
33
  import { cn } from "../../lib";
@@ -58,20 +77,6 @@ const STATE_BADGES: Record<IntegrationCardState, BadgeVariant> = {
58
77
  },
59
78
  };
60
79
 
61
- function getActionLabel(card: IntegrationCardData): string {
62
- if (card.isAddNew) return "Conectar";
63
- switch (card.state) {
64
- case "available":
65
- return "Conectar";
66
- case "connected":
67
- return "Configurar";
68
- case "expired":
69
- return "Reconectar";
70
- default:
71
- return "";
72
- }
73
- }
74
-
75
80
  // ---------------------------------------------------------------------------
76
81
  // Component
77
82
  // ---------------------------------------------------------------------------
@@ -79,25 +84,36 @@ function getActionLabel(card: IntegrationCardData): string {
79
84
  export interface IntegrationCardProps {
80
85
  card: IntegrationCardData;
81
86
  onConnect: (card: IntegrationCardData) => void;
87
+ onReconnect?: (card: IntegrationCardData) => void;
88
+ onDisconnect?: (card: IntegrationCardData) => void;
89
+ onDelete?: (card: IntegrationCardData) => void;
82
90
  }
83
91
 
84
- export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
92
+ export function IntegrationCard({
93
+ card,
94
+ onConnect,
95
+ onReconnect,
96
+ onDisconnect,
97
+ onDelete,
98
+ }: IntegrationCardProps) {
85
99
  const { definition, state, isAddNew, accountLabel } = card;
86
100
  const Icon = resolveIcon(definition.icon);
87
101
  const isComingSoon = state === "coming_soon";
88
- const actionLabel = getActionLabel(card);
102
+ const isConnected = state === "connected" || state === "expired";
89
103
 
90
- // "Add new" card uses a muted/outlined style
104
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
105
+
106
+ // "Add new" card — clean outline style with single "Conectar" button
91
107
  if (isAddNew) {
92
108
  return (
93
109
  <div
94
110
  className={cn(
95
- "group relative flex flex-col gap-3 rounded-xl border border-dashed bg-card/50 p-5 transition-shadow",
96
- "hover:shadow-md hover:border-solid hover:bg-card cursor-pointer",
111
+ "group relative flex flex-col gap-3 rounded-xl border bg-card/50 p-5 transition-all",
112
+ "hover:shadow-md hover:bg-card cursor-pointer",
97
113
  )}
98
114
  role="button"
99
115
  tabIndex={0}
100
- aria-label={`Adicionar conta ${definition.name}`}
116
+ aria-label={`Conectar ${definition.name}`}
101
117
  onClick={() => onConnect(card)}
102
118
  onKeyDown={(e) => {
103
119
  if (e.key === "Enter" || e.key === " ") {
@@ -107,24 +123,18 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
107
123
  }}
108
124
  >
109
125
  {/* Header row */}
110
- <div className="flex items-start justify-between gap-2">
126
+ <div className="flex items-start gap-3">
111
127
  <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/5 text-primary/60">
112
128
  <Icon className="h-5 w-5" />
113
129
  </div>
114
- <Badge variant="outline" className="text-xs bg-muted text-muted-foreground">
115
- Adicionar
116
- </Badge>
117
- </div>
118
-
119
- {/* Name + description */}
120
- <div className="space-y-1">
121
- <h3 className="text-sm font-semibold leading-tight text-muted-foreground">
122
- {definition.name}
123
- </h3>
124
- <p className="text-xs text-muted-foreground/70 leading-relaxed flex items-center gap-1">
125
- <Plus className="h-3 w-3" />
126
- Adicionar conta
127
- </p>
130
+ <div className="flex-1 min-w-0 space-y-0.5">
131
+ <h3 className="text-sm font-semibold leading-tight text-foreground">
132
+ {definition.name}
133
+ </h3>
134
+ <p className="text-xs text-muted-foreground leading-relaxed">
135
+ Conectar nova conta
136
+ </p>
137
+ </div>
128
138
  </div>
129
139
 
130
140
  {/* Footer */}
@@ -138,76 +148,132 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
138
148
  onConnect(card);
139
149
  }}
140
150
  >
141
- {actionLabel}
151
+ Conectar
142
152
  </Button>
143
153
  </div>
144
154
  </div>
145
155
  );
146
156
  }
147
157
 
148
- // Connected / expired / available card
149
- const badge = STATE_BADGES[state];
150
-
151
- return (
152
- <div
153
- className={cn(
154
- "group relative flex flex-col gap-3 rounded-xl border bg-card p-5 transition-shadow",
155
- isComingSoon
156
- ? "opacity-60 cursor-default"
157
- : "hover:shadow-md cursor-pointer",
158
- )}
159
- role="button"
160
- tabIndex={isComingSoon ? -1 : 0}
161
- aria-label={`${definition.name}${accountLabel ? ` — ${accountLabel}` : ""} — ${badge.label}`}
162
- aria-disabled={isComingSoon}
163
- onClick={() => !isComingSoon && onConnect(card)}
164
- onKeyDown={(e) => {
165
- if (!isComingSoon && (e.key === "Enter" || e.key === " ")) {
166
- e.preventDefault();
167
- onConnect(card);
168
- }
169
- }}
170
- >
171
- {/* Header row */}
172
- <div className="flex items-start justify-between gap-2">
173
- <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
174
- <Icon className="h-5 w-5" />
158
+ // Coming soon card
159
+ if (isComingSoon) {
160
+ const badge = STATE_BADGES[state];
161
+ return (
162
+ <div
163
+ className="group relative flex flex-col gap-3 rounded-xl border bg-card p-5 opacity-60 cursor-default"
164
+ aria-disabled
165
+ >
166
+ <div className="flex items-start justify-between gap-2">
167
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
168
+ <Icon className="h-5 w-5" />
169
+ </div>
170
+ <Badge variant="outline" className={cn("text-xs", badge.className)}>
171
+ {badge.label}
172
+ </Badge>
175
173
  </div>
176
- <Badge variant="outline" className={cn("text-xs", badge.className)}>
177
- {badge.label}
178
- </Badge>
179
- </div>
180
-
181
- {/* Name + account label */}
182
- <div className="space-y-1">
183
- <h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
184
- {accountLabel ? (
185
- <p className="text-xs text-muted-foreground leading-relaxed truncate" title={accountLabel}>
186
- {accountLabel}
187
- </p>
188
- ) : (
174
+ <div className="space-y-1">
175
+ <h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
189
176
  <p className="text-xs text-muted-foreground leading-relaxed">
190
177
  {definition.description}
191
178
  </p>
192
- )}
179
+ </div>
193
180
  </div>
181
+ );
182
+ }
194
183
 
195
- {/* Footer */}
196
- <div className="mt-auto flex items-center justify-end gap-2 pt-1">
197
- {!isComingSoon && (
198
- <Button
199
- variant={state === "expired" ? "destructive" : "outline"}
200
- size="sm"
201
- className="text-xs"
202
- onClick={(e) => {
203
- e.stopPropagation();
204
- onConnect(card);
205
- }}
206
- >
207
- {actionLabel}
208
- </Button>
209
- )}
184
+ // Connected / expired card
185
+ const badge = STATE_BADGES[state];
186
+
187
+ return (
188
+ <>
189
+ <div
190
+ className="group relative flex flex-col gap-3 rounded-xl border bg-card p-5 transition-shadow hover:shadow-md"
191
+ >
192
+ {/* Header row */}
193
+ <div className="flex items-start justify-between gap-2">
194
+ <div className="flex items-start gap-3 min-w-0 flex-1">
195
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
196
+ <Icon className="h-5 w-5" />
197
+ </div>
198
+ <div className="flex-1 min-w-0 space-y-0.5">
199
+ <h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
200
+ {accountLabel && (
201
+ <p className="text-xs text-muted-foreground leading-relaxed truncate" title={accountLabel}>
202
+ {accountLabel}
203
+ </p>
204
+ )}
205
+ </div>
206
+ </div>
207
+ <Badge variant="outline" className={cn("text-xs shrink-0", badge.className)}>
208
+ {badge.label}
209
+ </Badge>
210
+ </div>
211
+
212
+ {/* Footer with "Configurar" dropdown */}
213
+ <div className="mt-auto flex items-center justify-end gap-2 pt-1">
214
+ <DropdownMenu>
215
+ <DropdownMenuTrigger asChild>
216
+ <Button
217
+ variant="outline"
218
+ size="sm"
219
+ className="text-xs gap-1.5"
220
+ >
221
+ <Settings className="h-3.5 w-3.5" />
222
+ Configurar
223
+ </Button>
224
+ </DropdownMenuTrigger>
225
+ <DropdownMenuContent align="end">
226
+ <DropdownMenuItem
227
+ onClick={() => onReconnect?.(card)}
228
+ className="gap-2"
229
+ >
230
+ <RefreshCw className="h-4 w-4" />
231
+ Reconectar
232
+ </DropdownMenuItem>
233
+ <DropdownMenuItem
234
+ onClick={() => onDisconnect?.(card)}
235
+ className="gap-2"
236
+ >
237
+ <Unplug className="h-4 w-4" />
238
+ Desconectar
239
+ </DropdownMenuItem>
240
+ <DropdownMenuItem
241
+ onClick={() => setDeleteDialogOpen(true)}
242
+ className="gap-2 text-destructive focus:text-destructive"
243
+ >
244
+ <Trash2 className="h-4 w-4" />
245
+ Remover
246
+ </DropdownMenuItem>
247
+ </DropdownMenuContent>
248
+ </DropdownMenu>
249
+ </div>
210
250
  </div>
211
- </div>
251
+
252
+ {/* Delete confirmation dialog */}
253
+ <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
254
+ <AlertDialogContent>
255
+ <AlertDialogHeader>
256
+ <AlertDialogTitle>Remover integração?</AlertDialogTitle>
257
+ <AlertDialogDescription>
258
+ Esta ação vai remover a credencial
259
+ {accountLabel ? ` (${accountLabel})` : ""} de {definition.name}.
260
+ Esta ação não pode ser desfeita.
261
+ </AlertDialogDescription>
262
+ </AlertDialogHeader>
263
+ <AlertDialogFooter>
264
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
265
+ <AlertDialogAction
266
+ onClick={() => {
267
+ onDelete?.(card);
268
+ setDeleteDialogOpen(false);
269
+ }}
270
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
271
+ >
272
+ Remover
273
+ </AlertDialogAction>
274
+ </AlertDialogFooter>
275
+ </AlertDialogContent>
276
+ </AlertDialog>
277
+ </>
212
278
  );
213
279
  }
@@ -347,10 +347,13 @@ export function IntegrationWizard({
347
347
  //
348
348
  // For OAuth, the credential was already created by the backend callback.
349
349
  // The parent component handles any additional linking (agent_tool creation)
350
- // via the onComplete callback.
350
+ // and query invalidation via the onComplete callback.
351
+ //
352
+ // Call onComplete BEFORE closing the dialog so query invalidation
353
+ // triggers while the dialog is still mounted (avoids stale UI).
351
354
 
352
355
  onComplete();
353
- onOpenChange(false);
356
+
354
357
  toast.success(
355
358
  `${integration.name} ${isReconnect ? "reconectado" : "configurado"} com sucesso!`,
356
359
  );
@@ -4,11 +4,14 @@ import { useCallback } from "react";
4
4
  import type { GagentsHookConfig } from "../../hooks/types";
5
5
  import { useIntegrationState } from "../../hooks/use-integrations";
6
6
  import { useAgentTools, useAddAgentTool, useRemoveAgentTool } from "../../hooks/use-agent-tools";
7
+ import { useCreateTool, useTools } from "../../hooks/use-tools";
7
8
  import { Switch, Tooltip, TooltipContent, TooltipTrigger } from "@greatapps/greatauth-ui/ui";
8
9
  import { Plug, Loader2 } from "lucide-react";
9
10
  import type { LucideIcon } from "lucide-react";
10
11
  import { CalendarSync } from "lucide-react";
11
12
  import { cn } from "../../lib";
13
+ import type { IntegrationCardData } from "../../hooks/use-integrations";
14
+ import type { Tool } from "../../types";
12
15
 
13
16
  // ---------------------------------------------------------------------------
14
17
  // Icon mapping
@@ -23,6 +26,14 @@ function resolveIcon(name: string): LucideIcon {
23
26
  return ICON_MAP[name] ?? Plug;
24
27
  }
25
28
 
29
+ // ---------------------------------------------------------------------------
30
+ // Integration custom_instructions per slug
31
+ // ---------------------------------------------------------------------------
32
+
33
+ const INTEGRATION_INSTRUCTIONS: Record<string, string> = {
34
+ "google-calendar": `Você tem acesso ao Google Calendar através da integração google-calendar.\nFunções disponíveis:\n- google_calendar_setup_oauth: Configurar conexão OAuth\n- google_calendar_check_status: Verificar status da conexão\n- google_calendar_list_events: Listar eventos do calendário\n- google_calendar_create_event: Criar novo evento\n- google_calendar_update_event: Atualizar evento existente\n- google_calendar_delete_event: Cancelar/remover evento\n\nUse EXATAMENTE os nomes de função listados acima.`,
35
+ };
36
+
26
37
  // ---------------------------------------------------------------------------
27
38
  // Props
28
39
  // ---------------------------------------------------------------------------
@@ -42,10 +53,13 @@ export function IntegrationsTab({
42
53
  }: IntegrationsTabProps) {
43
54
  const { cards, isLoading } = useIntegrationState(config, agentId);
44
55
  const { data: agentToolsData, isLoading: agentToolsLoading } = useAgentTools(config, agentId);
56
+ const { data: toolsData } = useTools(config);
45
57
  const addAgentTool = useAddAgentTool(config);
46
58
  const removeAgentTool = useRemoveAgentTool(config);
59
+ const createTool = useCreateTool(config);
47
60
 
48
61
  const agentTools = agentToolsData?.data ?? [];
62
+ const allTools = toolsData?.data ?? [];
49
63
 
50
64
  // Only show connected credentials (account-level)
51
65
  const connectedCards = cards.filter(
@@ -53,22 +67,58 @@ export function IntegrationsTab({
53
67
  );
54
68
 
55
69
  const handleToggle = useCallback(
56
- (toolId: number, checked: boolean) => {
70
+ async (card: IntegrationCardData, checked: boolean) => {
57
71
  if (checked) {
58
- // Add agent_tool linking this agent to this tool
72
+ let toolId = card.tool?.id;
73
+
74
+ // If no tool record exists for this integration, create one on-the-fly
75
+ if (!toolId) {
76
+ const existingTool = allTools.find((t) => t.slug === card.definition.slug);
77
+ if (existingTool) {
78
+ toolId = existingTool.id;
79
+ } else {
80
+ try {
81
+ const result = await createTool.mutateAsync({
82
+ name: card.definition.name,
83
+ slug: card.definition.slug,
84
+ type: "integration",
85
+ description: card.definition.description,
86
+ });
87
+ const d = result?.data;
88
+ toolId = (Array.isArray(d) ? d[0]?.id : (d as Tool | undefined)?.id) ?? undefined;
89
+ if (!toolId) {
90
+ console.error("[IntegrationsTab] Failed to create tool — no ID returned");
91
+ return;
92
+ }
93
+ } catch (err) {
94
+ console.error("[IntegrationsTab] Error creating tool:", err);
95
+ return;
96
+ }
97
+ }
98
+ }
99
+
100
+ // Create agent_tool with custom_instructions for the integration
101
+ const customInstructions = INTEGRATION_INSTRUCTIONS[card.definition.slug];
59
102
  addAgentTool.mutate({
60
103
  idAgent: agentId,
61
- body: { id_tool: toolId, enabled: true },
104
+ body: {
105
+ id_tool: toolId,
106
+ enabled: true,
107
+ ...(customInstructions ? { custom_instructions: customInstructions } : {}),
108
+ },
62
109
  });
63
110
  } else {
64
111
  // Find the agent_tool to remove
65
- const agentTool = agentTools.find((at) => at.id_tool === toolId);
66
- if (agentTool) {
67
- removeAgentTool.mutate({ idAgent: agentId, id: agentTool.id });
112
+ const toolId = card.tool?.id;
113
+ if (toolId) {
114
+ const agentTool = agentTools.find((at) => at.id_tool === toolId);
115
+ if (agentTool) {
116
+ removeAgentTool.mutate({ idAgent: agentId, id: agentTool.id });
117
+ }
68
118
  }
69
119
  }
70
120
  },
71
- [agentTools, agentId, addAgentTool, removeAgentTool],
121
+ [agentTools, allTools, agentId, addAgentTool, removeAgentTool, createTool],
72
122
  );
73
123
 
74
124
  // Loading state
@@ -104,7 +154,7 @@ export function IntegrationsTab({
104
154
  {connectedCards.map((card) => {
105
155
  const Icon = resolveIcon(card.definition.icon);
106
156
  const isLinked = card.linkedToAgent;
107
- const isMutating = addAgentTool.isPending || removeAgentTool.isPending;
157
+ const isMutating = addAgentTool.isPending || removeAgentTool.isPending || createTool.isPending;
108
158
 
109
159
  return (
110
160
  <div
@@ -139,13 +189,11 @@ export function IntegrationsTab({
139
189
  )}
140
190
  </div>
141
191
 
142
- {/* Toggle */}
192
+ {/* Toggle — no longer blocked by missing tool record */}
143
193
  <Switch
144
194
  checked={isLinked}
145
- disabled={isMutating || !card.tool}
146
- onCheckedChange={(checked) =>
147
- card.tool && handleToggle(card.tool.id, checked)
148
- }
195
+ disabled={isMutating}
196
+ onCheckedChange={(checked) => handleToggle(card, checked)}
149
197
  aria-label={`${isLinked ? "Desativar" : "Ativar"} ${card.definition.name} para este agente`}
150
198
  />
151
199
  </div>
@@ -23,7 +23,7 @@ export function useAddAgentTool(config: GagentsHookConfig) {
23
23
  body,
24
24
  }: {
25
25
  idAgent: number;
26
- body: { id_tool: number; enabled?: boolean };
26
+ body: { id_tool: number; enabled?: boolean; custom_instructions?: string };
27
27
  }) => client.addAgentTool(config.accountId, idAgent, body),
28
28
  onSuccess: () => {
29
29
  queryClient.invalidateQueries({ queryKey: ["greatagents", "agent-tools"] });