@burdenoff/fluidgrids-fe-sdk 2026.1109.9 → 2026.1110.2

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 (56) hide show
  1. package/dist/{ExecutionDetailPage-2ORSV3YN.js → ExecutionDetailPage-UXQFBLF3.js} +14 -14
  2. package/dist/{ExecutionsListPage-MY45NECW.js → ExecutionsListPage-A4GCUNFR.js} +11 -11
  3. package/dist/{WorkflowBuilder-MWC2BCTQ.js → WorkflowBuilder-ZLVMV2UA.js} +21 -15
  4. package/dist/{WorkflowsList-6AG7CFZM.js → WorkflowsList-XPNGNR4A.js} +8 -8
  5. package/dist/{agentTools-5O2KPQYJ.js → agentTools-V26FOMTS.js} +5 -7
  6. package/dist/{chunk-ACME5GOU.js → chunk-2DBAQR6F.js} +2 -2
  7. package/dist/{chunk-MYE6CMGK.js → chunk-2H4XAES7.js} +3 -3
  8. package/dist/{chunk-IZSWG5G3.js → chunk-3F4DIVKT.js} +1 -1
  9. package/dist/chunk-3JGAI5YC.js +1437 -0
  10. package/dist/{chunk-MSNCU6US.js → chunk-5TGPH3CX.js} +4 -4
  11. package/dist/{chunk-U52PO4QQ.js → chunk-5V7PMLQN.js} +1320 -58
  12. package/dist/{chunk-TKSVFP36.js → chunk-5ZAOXCIQ.js} +6 -6
  13. package/dist/{chunk-POCNG3PM.js → chunk-6CPQVLHT.js} +15 -16
  14. package/dist/{chunk-2LERIIZM.js → chunk-7UFE2CHD.js} +308 -1026
  15. package/dist/{chunk-VAWYR6P4.js → chunk-A6Q4FWB6.js} +9 -1
  16. package/dist/{chunk-G4NAU6FF.js → chunk-CBX2CXGH.js} +8 -8
  17. package/dist/{chunk-5UA262ZO.js → chunk-CKF2FBUP.js} +1 -1
  18. package/dist/{chunk-5YLS3WH5.js → chunk-D4L5BN64.js} +1 -1
  19. package/dist/{chunk-VYXTP6YF.js → chunk-DDINUPO3.js} +2185 -5390
  20. package/dist/{chunk-DFFQYK2X.js → chunk-DIIGFBYT.js} +1 -1
  21. package/dist/{chunk-3T3C3475.js → chunk-G7BT24PD.js} +550 -3
  22. package/dist/chunk-HORSE7WX.js +54 -0
  23. package/dist/{chunk-7ADEPA4W.js → chunk-I3DBTNWQ.js} +6 -6
  24. package/dist/{chunk-HZ7FQCFU.js → chunk-JNSHR2J2.js} +1 -1
  25. package/dist/{chunk-ZXLXZFWO.js → chunk-K22NDWSD.js} +2 -2
  26. package/dist/{chunk-JXCJRBWD.js → chunk-K2X63CR2.js} +1 -5
  27. package/dist/{chunk-Q526TUOC.js → chunk-L5MZ74DA.js} +1 -1
  28. package/dist/{chunk-WKF72AS2.js → chunk-OVZA4IM7.js} +4 -4
  29. package/dist/{chunk-GR3QNLFS.js → chunk-OZNFRJKC.js} +5 -5
  30. package/dist/{chunk-NEB4Y7AX.js → chunk-PI4AW4T2.js} +241 -34
  31. package/dist/{chunk-TNMXKPJX.js → chunk-PICOXEUO.js} +4 -4
  32. package/dist/{chunk-QWDF72GZ.js → chunk-QGRVYPQC.js} +2 -2
  33. package/dist/{chunk-THIMC2ZD.js → chunk-WGUHISC2.js} +29 -11
  34. package/dist/{chunk-B3USL2JQ.js → chunk-X7XVRJK2.js} +2 -2
  35. package/dist/{chunk-4EZFAL24.js → chunk-XLU63IUB.js} +6 -6
  36. package/dist/{chunk-CX3LABFK.js → chunk-Z6D6YL7C.js} +1 -1
  37. package/dist/{chunk-NCXK2AO6.js → chunk-ZQTANHKA.js} +2 -2
  38. package/dist/chunk-ZZBZPBO5.js +1644 -0
  39. package/dist/executions.js +15 -15
  40. package/dist/history.js +7 -9
  41. package/dist/index.d.ts +367 -159
  42. package/dist/index.js +592 -68
  43. package/dist/{node-detail-view-ZFGS3FWU.js → node-detail-view-2AFXRNX6.js} +19 -864
  44. package/dist/nodes.js +2 -2
  45. package/dist/observability.js +7 -7
  46. package/dist/{quick-config-panel-ZDGYZR7Y.js → quick-config-panel-U4AELSCT.js} +12 -54
  47. package/dist/settings.js +9 -9
  48. package/dist/templates.js +21 -14
  49. package/dist/workflow-builder.js +22 -16
  50. package/dist/workflow-preview.js +2 -2
  51. package/dist/workflows-list.js +8 -8
  52. package/package.json +1 -1
  53. package/dist/chunk-GTEBQQ7J.js +0 -24
  54. package/dist/chunk-O2OFNGAF.js +0 -338
  55. package/dist/chunk-QTOPG7SR.js +0 -501
  56. package/dist/chunk-U7UNWR3D.js +0 -260
@@ -0,0 +1,1644 @@
1
+ import { emptyAgentForm, agentToForm, describeRule, formToInput, AgentFormError, DEFAULT_GRANT_DAYS, MAX_GRANT_DAYS, grantDays } from './chunk-WZ3EJVS6.js';
2
+ import { SdkPermissionGate, FLUIDGRIDS_PERMISSIONS } from './chunk-ERT444RG.js';
3
+ import { replaceAdapterConnections, connectionsForAdapter, matchesAdapterProvider, newConnectionProviderKey, isOptionalAdapter } from './chunk-DDINUPO3.js';
4
+ import { useTr } from './chunk-UULVIT25.js';
5
+ import { getFluidGridsClient } from './chunk-PDODDDHY.js';
6
+ import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
7
+ import { getFluidGridsConfig } from './chunk-UEG2WC3G.js';
8
+ import { useState, useCallback, useEffect, useMemo } from 'react';
9
+ import { gql } from '@apollo/client';
10
+ import { ConnectionDialog, ProviderIcon } from '@burdenoff/fe-libs/connections';
11
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+ import { Icon } from '@iconify/react';
13
+ import { DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Label, Input, Switch, Textarea, Checkbox } from '@burdenoff/fe-libs/ui';
14
+ import { formatDistanceToNow } from 'date-fns';
15
+ import { Button as Button$1 } from '@burdenoff/fe-libs/button';
16
+ import { toast } from 'sonner';
17
+
18
+ var PROVIDER_FIELDS = gql`
19
+ fragment ProviderFields on IntegrationProvider {
20
+ id
21
+ key
22
+ name
23
+ description
24
+ category
25
+ authType
26
+ icon
27
+ color
28
+ website
29
+ docsUrl
30
+ isBuiltIn
31
+ isEnabled
32
+ availableScopes
33
+ # How this provider can be authenticated, as data. The builder renders the
34
+ # form from this rather than from a per-provider React component, so a
35
+ # provider or a new method added by an administrator shows up here with no
36
+ # frontend change.
37
+ authMethods {
38
+ key
39
+ displayName
40
+ description
41
+ flow
42
+ authType
43
+ configSchema
44
+ availableScopes
45
+ defaultScopes
46
+ isConfigured
47
+ isEnabled
48
+ displayOrder
49
+ }
50
+ }
51
+ `;
52
+ var CONNECTION_FIELDS = gql`
53
+ ${PROVIDER_FIELDS}
54
+ fragment ConnectionFields on IntegrationConnection {
55
+ id
56
+ name
57
+ description
58
+ providerId
59
+ provider {
60
+ ...ProviderFields
61
+ }
62
+ status
63
+ lastHealthCheck
64
+ lastUsedAt
65
+ errorMessage
66
+ externalAccountId
67
+ externalAccountEmail
68
+ externalAccountName
69
+ grantedScopes
70
+ createdBy
71
+ createdAt
72
+ updatedAt
73
+ visibility
74
+ myAccess
75
+ canUse
76
+ }
77
+ `;
78
+ var GET_CONNECTIONS = gql`
79
+ ${CONNECTION_FIELDS}
80
+ query GetConnections(
81
+ $filter: ConnectionFilterInput
82
+ $pagination: IntegrationsPaginationInput
83
+ ) {
84
+ getConnections(filter: $filter, pagination: $pagination) {
85
+ items {
86
+ ...ConnectionFields
87
+ }
88
+ total
89
+ hasMore
90
+ }
91
+ }
92
+ `;
93
+ var GET_CONNECTION = gql`
94
+ ${CONNECTION_FIELDS}
95
+ query GetConnection($id: ID!) {
96
+ getConnection(id: $id) {
97
+ ...ConnectionFields
98
+ }
99
+ }
100
+ `;
101
+ var GET_CONNECTIONS_BY_PROVIDER = gql`
102
+ ${CONNECTION_FIELDS}
103
+ query GetConnectionsByProvider(
104
+ $providerId: ID!
105
+ $pagination: IntegrationsPaginationInput
106
+ ) {
107
+ getConnectionsByProvider(providerId: $providerId, pagination: $pagination) {
108
+ items {
109
+ ...ConnectionFields
110
+ }
111
+ total
112
+ hasMore
113
+ }
114
+ }
115
+ `;
116
+ var GET_PROVIDERS = gql`
117
+ ${PROVIDER_FIELDS}
118
+ query GetProviders(
119
+ $filter: ProviderFilterInput
120
+ $pagination: IntegrationsPaginationInput
121
+ ) {
122
+ getProviders(filter: $filter, pagination: $pagination) {
123
+ items {
124
+ ...ProviderFields
125
+ }
126
+ total
127
+ hasMore
128
+ }
129
+ }
130
+ `;
131
+ var INITIATE_OAUTH = gql`
132
+ mutation InitiateOAuth($input: InitiateOAuthInput!) {
133
+ initiateOAuth(input: $input) {
134
+ redirectUrl
135
+ state
136
+ }
137
+ }
138
+ `;
139
+ var CREATE_CONNECTION = gql`
140
+ ${CONNECTION_FIELDS}
141
+ mutation CreateConnection($input: CreateConnectionInput!) {
142
+ createConnection(input: $input) {
143
+ ...ConnectionFields
144
+ }
145
+ }
146
+ `;
147
+ var CONNECTIONS_PAGE_SIZE = 100;
148
+ async function fetchAllConnectionPages(fetchPage) {
149
+ const items = [];
150
+ const seen = /* @__PURE__ */ new Set();
151
+ let total = 0;
152
+ let hasMore = false;
153
+ for (let offset = 0; ; offset += CONNECTIONS_PAGE_SIZE) {
154
+ const result = await fetchPage(offset);
155
+ const fresh = (result?.items ?? []).filter((item) => !seen.has(item.id));
156
+ for (const item of fresh) {
157
+ seen.add(item.id);
158
+ items.push(item);
159
+ }
160
+ total = result?.total ?? items.length;
161
+ hasMore = result?.hasMore ?? false;
162
+ if (!hasMore || fresh.length === 0 || items.length >= total) break;
163
+ }
164
+ return { items, total, hasMore: hasMore && items.length < total };
165
+ }
166
+ function useConnections(filter) {
167
+ const [connections, setConnections] = useState([]);
168
+ const [total, setTotal] = useState(0);
169
+ const [hasMore, setHasMore] = useState(false);
170
+ const [loading, setLoading] = useState(true);
171
+ const [error, setError] = useState(null);
172
+ const filterKey = JSON.stringify(filter);
173
+ const fetchData = useCallback(async () => {
174
+ setLoading(true);
175
+ setError(null);
176
+ try {
177
+ const client = getFluidGridsClient();
178
+ const parsedFilter = filterKey ? JSON.parse(filterKey) : void 0;
179
+ const list = await fetchAllConnectionPages(async (offset) => {
180
+ const result = await client.query({
181
+ query: GET_CONNECTIONS,
182
+ variables: {
183
+ filter: parsedFilter,
184
+ pagination: { limit: CONNECTIONS_PAGE_SIZE, offset }
185
+ },
186
+ fetchPolicy: "network-only"
187
+ });
188
+ return result.data?.getConnections;
189
+ });
190
+ setConnections(list.items);
191
+ setTotal(list.total);
192
+ setHasMore(list.hasMore);
193
+ } catch (err) {
194
+ setError(err);
195
+ } finally {
196
+ setLoading(false);
197
+ }
198
+ }, [filterKey]);
199
+ useEffect(() => {
200
+ fetchData();
201
+ }, [fetchData]);
202
+ return {
203
+ connections,
204
+ total,
205
+ hasMore,
206
+ loading,
207
+ error,
208
+ refetch: fetchData
209
+ };
210
+ }
211
+ function useConnection(connectionId) {
212
+ const [connection, setConnection] = useState(
213
+ null
214
+ );
215
+ const [loading, setLoading] = useState(true);
216
+ const [error, setError] = useState(null);
217
+ const fetchData = useCallback(async () => {
218
+ if (!connectionId) {
219
+ setLoading(false);
220
+ return;
221
+ }
222
+ setLoading(true);
223
+ setError(null);
224
+ try {
225
+ const client = getFluidGridsClient();
226
+ const result = await client.query({
227
+ query: GET_CONNECTION,
228
+ variables: { id: connectionId },
229
+ fetchPolicy: "cache-first"
230
+ });
231
+ setConnection(result.data?.getConnection ?? null);
232
+ } catch (err) {
233
+ setError(err);
234
+ } finally {
235
+ setLoading(false);
236
+ }
237
+ }, [connectionId]);
238
+ useEffect(() => {
239
+ fetchData();
240
+ }, [fetchData]);
241
+ return {
242
+ connection,
243
+ loading,
244
+ error,
245
+ refetch: fetchData
246
+ };
247
+ }
248
+ function useConnectionsByProvider(providerId) {
249
+ const [connections, setConnections] = useState([]);
250
+ const [total, setTotal] = useState(0);
251
+ const [loading, setLoading] = useState(true);
252
+ const [error, setError] = useState(null);
253
+ const fetchData = useCallback(async () => {
254
+ if (!providerId) {
255
+ setLoading(false);
256
+ return;
257
+ }
258
+ setLoading(true);
259
+ setError(null);
260
+ try {
261
+ const client = getFluidGridsClient();
262
+ const result = await client.query({
263
+ query: GET_CONNECTIONS_BY_PROVIDER,
264
+ variables: { providerId, pagination: { limit: 100 } },
265
+ fetchPolicy: "network-only"
266
+ });
267
+ setConnections(result.data?.getConnectionsByProvider?.items ?? []);
268
+ setTotal(result.data?.getConnectionsByProvider?.total ?? 0);
269
+ } catch (err) {
270
+ setError(err);
271
+ } finally {
272
+ setLoading(false);
273
+ }
274
+ }, [providerId]);
275
+ useEffect(() => {
276
+ fetchData();
277
+ }, [fetchData]);
278
+ return {
279
+ connections,
280
+ total,
281
+ loading,
282
+ error,
283
+ refetch: fetchData
284
+ };
285
+ }
286
+ function useProviders(filter) {
287
+ const [providers, setProviders] = useState([]);
288
+ const [total, setTotal] = useState(0);
289
+ const [loading, setLoading] = useState(true);
290
+ const [error, setError] = useState(null);
291
+ const filterKey = JSON.stringify(filter);
292
+ const fetchData = useCallback(async () => {
293
+ setLoading(true);
294
+ setError(null);
295
+ try {
296
+ const client = getFluidGridsClient();
297
+ const parsedFilter = filterKey ? JSON.parse(filterKey) : void 0;
298
+ const result = await client.query({
299
+ query: GET_PROVIDERS,
300
+ variables: {
301
+ filter: parsedFilter,
302
+ pagination: { limit: 100 }
303
+ },
304
+ fetchPolicy: "cache-first"
305
+ });
306
+ setProviders(result.data?.getProviders?.items ?? []);
307
+ setTotal(result.data?.getProviders?.total ?? 0);
308
+ } catch (err) {
309
+ setError(err);
310
+ } finally {
311
+ setLoading(false);
312
+ }
313
+ }, [filterKey]);
314
+ useEffect(() => {
315
+ fetchData();
316
+ }, [fetchData]);
317
+ return {
318
+ providers,
319
+ total,
320
+ loading,
321
+ error,
322
+ refetch: fetchData
323
+ };
324
+ }
325
+ function useCreateConnection() {
326
+ const [loading, setLoading] = useState(false);
327
+ const [error, setError] = useState(null);
328
+ const createConnection = useCallback(
329
+ async (input) => {
330
+ setLoading(true);
331
+ setError(null);
332
+ try {
333
+ const client = getFluidGridsClient();
334
+ const result = throwIfResultError(
335
+ await client.mutate({
336
+ mutation: CREATE_CONNECTION,
337
+ variables: { input }
338
+ })
339
+ );
340
+ return result.data?.createConnection ?? null;
341
+ } catch (err) {
342
+ setError(err);
343
+ return null;
344
+ } finally {
345
+ setLoading(false);
346
+ }
347
+ },
348
+ []
349
+ );
350
+ return {
351
+ createConnection,
352
+ loading,
353
+ error
354
+ };
355
+ }
356
+ function useInitiateOAuth() {
357
+ const [loading, setLoading] = useState(false);
358
+ const [error, setError] = useState(null);
359
+ const initiateOAuth = useCallback(
360
+ async (input) => {
361
+ setLoading(true);
362
+ setError(null);
363
+ try {
364
+ const client = getFluidGridsClient();
365
+ const result = throwIfResultError(
366
+ await client.mutate({
367
+ mutation: INITIATE_OAUTH,
368
+ variables: { input }
369
+ })
370
+ );
371
+ return result.data?.initiateOAuth ?? null;
372
+ } catch (err) {
373
+ setError(err);
374
+ return null;
375
+ } finally {
376
+ setLoading(false);
377
+ }
378
+ },
379
+ []
380
+ );
381
+ return {
382
+ initiateOAuth,
383
+ loading,
384
+ error
385
+ };
386
+ }
387
+
388
+ // src/modules/connections/utils/provider-helpers.ts
389
+ function getOAuthCallbackUrl() {
390
+ const path = "/integrations/oauth/callback";
391
+ const gatewayBase = getFluidGridsConfig().apiGatewayUrl;
392
+ if (gatewayBase) {
393
+ try {
394
+ return new URL(path, gatewayBase).toString();
395
+ } catch {
396
+ }
397
+ }
398
+ if (typeof window !== "undefined" && window.location.hostname === "localhost") {
399
+ return `http://localhost:3713${path}`;
400
+ }
401
+ if (typeof window !== "undefined") {
402
+ return `${window.location.origin}${path}`;
403
+ }
404
+ return path;
405
+ }
406
+ var SDK_CATEGORIES = {
407
+ cloud_storage: "CLOUD_STORAGE",
408
+ communication: "COMMUNICATION",
409
+ productivity: "PRODUCTIVITY",
410
+ crm: "CRM",
411
+ email: "EMAIL",
412
+ calendar: "CALENDAR",
413
+ developer_tools: "DEVELOPMENT",
414
+ social: "SOCIAL",
415
+ finance: "PAYMENT",
416
+ analytics: "ANALYTICS"
417
+ };
418
+ var SDK_AUTH_TYPES = {
419
+ oauth2: "OAUTH2",
420
+ api_key: "API_KEY",
421
+ basic_auth: "BASIC_AUTH",
422
+ bearer_token: "BEARER_TOKEN"
423
+ };
424
+ function toSdkProvider(provider) {
425
+ return {
426
+ id: provider.id,
427
+ key: provider.key,
428
+ name: provider.name,
429
+ description: provider.description,
430
+ category: SDK_CATEGORIES[provider.category] ?? "CUSTOM",
431
+ authType: SDK_AUTH_TYPES[provider.authType] ?? "CUSTOM",
432
+ icon: provider.icon,
433
+ website: provider.website,
434
+ docsUrl: provider.docsUrl,
435
+ isBuiltIn: true,
436
+ isEnabled: provider.isEnabled
437
+ };
438
+ }
439
+ function ConnectionFormDialog({
440
+ open,
441
+ onClose,
442
+ providerKey,
443
+ providerId,
444
+ defaultName,
445
+ onConnectionCreated,
446
+ onError
447
+ }) {
448
+ const tr = useTr();
449
+ const handleCreated = useCallback(
450
+ (connection) => {
451
+ onConnectionCreated?.({
452
+ id: connection.id,
453
+ name: connection.name,
454
+ status: connection.status,
455
+ provider: toSdkProvider(connection.provider)
456
+ });
457
+ },
458
+ [onConnectionCreated]
459
+ );
460
+ return /* @__PURE__ */ jsx(
461
+ ConnectionDialog,
462
+ {
463
+ open,
464
+ onClose,
465
+ client: getFluidGridsClient(),
466
+ callbackUrl: getOAuthCallbackUrl(),
467
+ translate: tr,
468
+ providerKey: providerKey ?? null,
469
+ providerId: providerId ?? null,
470
+ providerSource: "catalog",
471
+ requireVault: false,
472
+ defaultName,
473
+ showSharingTab: true,
474
+ defaultVisibility: "RESTRICTED",
475
+ onConnectionCreated: handleCreated,
476
+ onError
477
+ }
478
+ );
479
+ }
480
+ var AGENT_FIELDS = `
481
+ id
482
+ name
483
+ description
484
+ appId
485
+ toolAllowlist { nodeType operations }
486
+ connectionScope
487
+ requireToolApproval
488
+ ownerUserId
489
+ status
490
+ createdAt
491
+ updatedAt
492
+ `;
493
+ var GRANT_FIELDS = `
494
+ agentDefinitionId
495
+ userId
496
+ grantedBy
497
+ grantedAt
498
+ expiresAt
499
+ status
500
+ isMine
501
+ `;
502
+ var AGENT_DEFINITIONS = gql`
503
+ query AgentDefinitions {
504
+ agentDefinitions {
505
+ ${AGENT_FIELDS}
506
+ }
507
+ }
508
+ `;
509
+ var AGENT_GRANTS = gql`
510
+ query AgentDefinitionGrants($agentDefinitionId: ID!) {
511
+ agentDefinitionGrants(agentDefinitionId: $agentDefinitionId) {
512
+ ${GRANT_FIELDS}
513
+ }
514
+ }
515
+ `;
516
+ var CREATE_AGENT = gql`
517
+ mutation CreateAgentDefinition($input: AgentDefinitionInput!) {
518
+ createAgentDefinition(input: $input) {
519
+ ${AGENT_FIELDS}
520
+ }
521
+ }
522
+ `;
523
+ var UPDATE_AGENT = gql`
524
+ mutation UpdateAgentDefinition($id: ID!, $input: AgentDefinitionInput!) {
525
+ updateAgentDefinition(id: $id, input: $input) {
526
+ ${AGENT_FIELDS}
527
+ }
528
+ }
529
+ `;
530
+ var DELETE_AGENT = gql`
531
+ mutation DeleteAgentDefinition($id: ID!) {
532
+ deleteAgentDefinition(id: $id)
533
+ }
534
+ `;
535
+ var GRANT_AGENT = gql`
536
+ mutation GrantAgentDefinition($agentDefinitionId: ID!, $expiresInDays: Int) {
537
+ grantAgentDefinition(agentDefinitionId: $agentDefinitionId, expiresInDays: $expiresInDays) {
538
+ ${GRANT_FIELDS}
539
+ }
540
+ }
541
+ `;
542
+ var REVOKE_MY_GRANT = gql`
543
+ mutation RevokeMyAgentGrant($agentDefinitionId: ID!) {
544
+ revokeMyAgentGrant(agentDefinitionId: $agentDefinitionId)
545
+ }
546
+ `;
547
+ var REVOKE_GRANT = gql`
548
+ mutation RevokeAgentGrant($agentDefinitionId: ID!, $userId: String!) {
549
+ revokeAgentGrant(agentDefinitionId: $agentDefinitionId, userId: $userId)
550
+ }
551
+ `;
552
+ var AGENTS_USING_CONNECTION = gql`
553
+ query AgentDefinitionsUsingConnection($connectionId: ID!) {
554
+ agentDefinitionsUsingConnection(connectionId: $connectionId) {
555
+ id
556
+ name
557
+ status
558
+ }
559
+ }
560
+ `;
561
+ function asError(err) {
562
+ return err instanceof Error ? err : new Error(String(err));
563
+ }
564
+ function useAgentDefinitionList() {
565
+ const [agents, setAgents] = useState([]);
566
+ const [loading, setLoading] = useState(true);
567
+ const [error, setError] = useState(null);
568
+ const reload = useCallback(async () => {
569
+ try {
570
+ const result = await getFluidGridsClient().query({ query: AGENT_DEFINITIONS, fetchPolicy: "network-only" });
571
+ throwIfResultError(result);
572
+ setAgents(result.data?.agentDefinitions ?? []);
573
+ setError(null);
574
+ } catch (err) {
575
+ setError(asError(err));
576
+ } finally {
577
+ setLoading(false);
578
+ }
579
+ }, []);
580
+ useEffect(() => {
581
+ void reload();
582
+ }, [reload]);
583
+ return { agents, loading, error, reload };
584
+ }
585
+ function useAgentDefinitions() {
586
+ const list = useAgentDefinitionList();
587
+ const { reload } = list;
588
+ const save = useCallback(
589
+ async (id, input) => {
590
+ const client = getFluidGridsClient();
591
+ const result = id ? await client.mutate({
592
+ mutation: UPDATE_AGENT,
593
+ variables: { id, input }
594
+ }) : await client.mutate({ mutation: CREATE_AGENT, variables: { input } });
595
+ throwIfResultError(result);
596
+ await reload();
597
+ },
598
+ [reload]
599
+ );
600
+ const remove = useCallback(
601
+ async (id) => {
602
+ const result = await getFluidGridsClient().mutate({
603
+ mutation: DELETE_AGENT,
604
+ variables: { id }
605
+ });
606
+ throwIfResultError(result);
607
+ await reload();
608
+ },
609
+ [reload]
610
+ );
611
+ const grants = useCallback(async (agentDefinitionId) => {
612
+ const result = await getFluidGridsClient().query({
613
+ query: AGENT_GRANTS,
614
+ variables: { agentDefinitionId },
615
+ fetchPolicy: "network-only"
616
+ });
617
+ throwIfResultError(result);
618
+ return result.data?.agentDefinitionGrants ?? [];
619
+ }, []);
620
+ const grant = useCallback(
621
+ async (agentDefinitionId, expiresInDays) => {
622
+ const result = await getFluidGridsClient().mutate({
623
+ mutation: GRANT_AGENT,
624
+ variables: { agentDefinitionId, expiresInDays }
625
+ });
626
+ throwIfResultError(result);
627
+ },
628
+ []
629
+ );
630
+ const revoke = useCallback(
631
+ async (agentDefinitionId, userId) => {
632
+ const result = userId ? await getFluidGridsClient().mutate({
633
+ mutation: REVOKE_GRANT,
634
+ variables: { agentDefinitionId, userId }
635
+ }) : await getFluidGridsClient().mutate({
636
+ mutation: REVOKE_MY_GRANT,
637
+ variables: { agentDefinitionId }
638
+ });
639
+ throwIfResultError(result);
640
+ },
641
+ []
642
+ );
643
+ return { ...list, save, remove, grants, grant, revoke };
644
+ }
645
+ function useAgentsUsingConnection(connectionId) {
646
+ const [agents, setAgents] = useState([]);
647
+ useEffect(() => {
648
+ if (!connectionId) {
649
+ setAgents([]);
650
+ return;
651
+ }
652
+ let cancelled = false;
653
+ getFluidGridsClient().query({
654
+ query: AGENTS_USING_CONNECTION,
655
+ variables: { connectionId },
656
+ fetchPolicy: "network-only"
657
+ }).then((result) => {
658
+ throwIfResultError(result);
659
+ if (!cancelled) {
660
+ setAgents(result.data?.agentDefinitionsUsingConnection ?? []);
661
+ }
662
+ }).catch(() => {
663
+ if (!cancelled) setAgents([]);
664
+ });
665
+ return () => {
666
+ cancelled = true;
667
+ };
668
+ }, [connectionId]);
669
+ return agents;
670
+ }
671
+ function ConnectionAgentUsage({
672
+ connectionId,
673
+ connectionName
674
+ }) {
675
+ const tr = useTr();
676
+ const agents = useAgentsUsingConnection(connectionId);
677
+ if (agents.length === 0) return null;
678
+ return /* @__PURE__ */ jsxs(
679
+ "div",
680
+ {
681
+ className: "flex items-start gap-1.5 text-xs text-muted-foreground",
682
+ "data-testid": `connection-agent-usage-${connectionId}`,
683
+ children: [
684
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:robot-outline", className: "w-3.5 h-3.5 mt-0.5 shrink-0" }),
685
+ /* @__PURE__ */ jsxs("span", { children: [
686
+ connectionName ? tr(
687
+ "fluidgrids.agents.usedBy.namedLabel",
688
+ "{{connection}} is used by agents:",
689
+ { connection: connectionName }
690
+ ) : tr("fluidgrids.agents.usedBy.label", "Used by agents:"),
691
+ " ",
692
+ agents.map(
693
+ (agent) => agent.status === "ACTIVE" ? agent.name : tr("fluidgrids.agents.usedBy.disabled", "{{name}} (disabled)", {
694
+ name: agent.name
695
+ })
696
+ ).join(", ")
697
+ ] })
698
+ ]
699
+ }
700
+ );
701
+ }
702
+ function formatAge(dateString) {
703
+ try {
704
+ return formatDistanceToNow(new Date(dateString), { addSuffix: true });
705
+ } catch {
706
+ return "";
707
+ }
708
+ }
709
+ function getStatusColor(status) {
710
+ switch (status) {
711
+ case "ACTIVE":
712
+ return "text-[var(--color-status-success-text)]";
713
+ case "INACTIVE":
714
+ return "text-text-secondary";
715
+ case "ERROR":
716
+ case "REVOKED":
717
+ return "text-[var(--color-status-error-text)]";
718
+ case "EXPIRED":
719
+ case "PENDING_AUTH":
720
+ return "text-[var(--color-status-warning-text)]";
721
+ default:
722
+ return "text-text-secondary";
723
+ }
724
+ }
725
+ function getStatusIcon(status) {
726
+ switch (status) {
727
+ case "ACTIVE":
728
+ return "mdi:check-circle";
729
+ case "INACTIVE":
730
+ return "mdi:pause-circle";
731
+ case "ERROR":
732
+ case "REVOKED":
733
+ return "mdi:alert-circle";
734
+ case "EXPIRED":
735
+ return "mdi:clock-alert";
736
+ case "PENDING_AUTH":
737
+ return "mdi:account-clock";
738
+ default:
739
+ return "mdi:help-circle";
740
+ }
741
+ }
742
+ function ConnectionChip({
743
+ connection,
744
+ connectionData,
745
+ onRemove
746
+ }) {
747
+ const tr = useTr();
748
+ const name = connectionData?.name || connection.connectionId;
749
+ const status = connectionData?.status;
750
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2 py-1 bg-accent/30 rounded-md text-sm group", children: [
751
+ /* @__PURE__ */ jsx(
752
+ ProviderIcon,
753
+ {
754
+ variant: "bare",
755
+ provider: connectionData?.provider ?? {
756
+ key: connection.adapter,
757
+ icon: void 0,
758
+ category: "CUSTOM"
759
+ },
760
+ size: 16
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsx("span", { className: "truncate max-w-32", children: name }),
764
+ status && /* @__PURE__ */ jsx(
765
+ Icon,
766
+ {
767
+ icon: getStatusIcon(status),
768
+ className: `w-3.5 h-3.5 ${getStatusColor(status)}`
769
+ }
770
+ ),
771
+ /* @__PURE__ */ jsx(
772
+ "button",
773
+ {
774
+ type: "button",
775
+ "aria-label": tr(
776
+ "fluidgrids.connections.connectionSelector.removeConnection",
777
+ "Remove connection"
778
+ ),
779
+ onClick: (e) => {
780
+ e.stopPropagation();
781
+ onRemove();
782
+ },
783
+ className: "text-text-secondary hover:text-status-error-text opacity-0 group-hover:opacity-100 transition-opacity",
784
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "w-3.5 h-3.5" })
785
+ }
786
+ )
787
+ ] });
788
+ }
789
+ function ConnectionSelector(props) {
790
+ const connectionList = useConnections();
791
+ if (props.adapters.length <= 1) {
792
+ return /* @__PURE__ */ jsx(AdapterConnectionSelector, { ...props, connectionList });
793
+ }
794
+ return /* @__PURE__ */ jsx("div", { className: props.compact ? "space-y-2" : "space-y-3", children: props.adapters.map((adapter) => /* @__PURE__ */ jsxs("fieldset", { className: "min-w-0 space-y-1.5", children: [
795
+ /* @__PURE__ */ jsx("legend", { className: "text-xs font-medium text-text-secondary", children: adapter.name }),
796
+ /* @__PURE__ */ jsx(
797
+ AdapterConnectionSelector,
798
+ {
799
+ ...props,
800
+ adapters: [adapter],
801
+ selectedConnections: connectionsForAdapter(
802
+ props.selectedConnections,
803
+ adapter.name
804
+ ),
805
+ onChange: (connections) => props.onChange(
806
+ replaceAdapterConnections(
807
+ props.selectedConnections,
808
+ adapter.name,
809
+ connections.map((connection) => connection.connectionId)
810
+ )
811
+ ),
812
+ showRequired: false,
813
+ connectionList
814
+ }
815
+ )
816
+ ] }, adapter.name)) });
817
+ }
818
+ function AdapterConnectionSelector({
819
+ adapters,
820
+ selectedConnections,
821
+ onChange,
822
+ multiple = false,
823
+ showStatus = true,
824
+ compact = false,
825
+ showRequired = true,
826
+ connectionList
827
+ }) {
828
+ const tr = useTr();
829
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
830
+ const [dialogOpen, setDialogOpen] = useState(false);
831
+ const [dialogProviderKey, setDialogProviderKey] = useState();
832
+ const { connections, loading: connectionsLoading, refetch } = connectionList;
833
+ const availableConnections = useMemo(() => {
834
+ if (!adapters || adapters.length === 0) return [];
835
+ return connections.filter(
836
+ (connection) => (
837
+ // BOFF-9726: an admin sees everyone's private connections but cannot
838
+ // run workflows with them, so only usable ones are offered for a node.
839
+ connection.canUse !== false && adapters.some(
840
+ (adapter) => matchesAdapterProvider(adapter, connection.provider?.key ?? "")
841
+ )
842
+ )
843
+ );
844
+ }, [connections, adapters]);
845
+ const mergedConnections = useMemo(() => {
846
+ const allConnections = [...availableConnections];
847
+ selectedConnections.forEach((selected) => {
848
+ const existsInAvailable = availableConnections.some(
849
+ (c) => c.id === selected.connectionId
850
+ );
851
+ if (!existsInAvailable) {
852
+ const fullConn = connections.find(
853
+ (c) => c.id === selected.connectionId
854
+ );
855
+ if (fullConn && fullConn.canUse !== false) {
856
+ allConnections.push(fullConn);
857
+ }
858
+ }
859
+ });
860
+ return allConnections;
861
+ }, [availableConnections, selectedConnections, connections]);
862
+ const groupedConnections = useMemo(() => {
863
+ const groups = {};
864
+ mergedConnections.forEach((conn) => {
865
+ const providerName = conn.provider?.name || "Other";
866
+ if (!groups[providerName]) {
867
+ groups[providerName] = [];
868
+ }
869
+ groups[providerName].push(conn);
870
+ });
871
+ return groups;
872
+ }, [mergedConnections]);
873
+ const handleSelect = useCallback(
874
+ (connection) => {
875
+ const providerKey = connection.provider?.key || "";
876
+ const adapter = adapters.find(
877
+ (candidate) => matchesAdapterProvider(candidate, providerKey)
878
+ );
879
+ if (!adapter) return;
880
+ const newSelection = {
881
+ connectionId: connection.id,
882
+ adapter: adapter.name
883
+ };
884
+ if (multiple) {
885
+ const isSelected = selectedConnections.some(
886
+ (c) => c.connectionId === connection.id
887
+ );
888
+ if (isSelected) {
889
+ onChange(
890
+ selectedConnections.filter((c) => c.connectionId !== connection.id)
891
+ );
892
+ } else {
893
+ onChange([...selectedConnections, newSelection]);
894
+ }
895
+ } else {
896
+ onChange([newSelection]);
897
+ setIsDropdownOpen(false);
898
+ }
899
+ },
900
+ [adapters, selectedConnections, onChange, multiple]
901
+ );
902
+ const handleRemove = useCallback(
903
+ (connectionId) => {
904
+ onChange(
905
+ selectedConnections.filter((c) => c.connectionId !== connectionId)
906
+ );
907
+ },
908
+ [selectedConnections, onChange]
909
+ );
910
+ const handleNewConnection = useCallback((providerKey) => {
911
+ setDialogProviderKey(providerKey);
912
+ setDialogOpen(true);
913
+ setIsDropdownOpen(false);
914
+ }, []);
915
+ const handleConnectionCreated = useCallback(
916
+ (conn) => {
917
+ const providerKey = conn.provider?.key || "";
918
+ const adapter = adapters.find(
919
+ (candidate) => matchesAdapterProvider(candidate, providerKey)
920
+ );
921
+ if (adapter) {
922
+ const newSelection = { connectionId: conn.id, adapter: adapter.name };
923
+ if (multiple) {
924
+ onChange([...selectedConnections, newSelection]);
925
+ } else {
926
+ onChange([newSelection]);
927
+ }
928
+ }
929
+ setDialogOpen(false);
930
+ refetch();
931
+ },
932
+ [adapters, selectedConnections, onChange, multiple, refetch]
933
+ );
934
+ const getConnectionData = useCallback(
935
+ (connectionId) => connections.find((c) => c.id === connectionId),
936
+ [connections]
937
+ );
938
+ if (!adapters || adapters.length === 0) {
939
+ return null;
940
+ }
941
+ return /* @__PURE__ */ jsxs("div", { className: compact ? "space-y-2" : "space-y-3", children: [
942
+ selectedConnections.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedConnections.map((conn) => {
943
+ const connData = getConnectionData(conn.connectionId);
944
+ return /* @__PURE__ */ jsx(
945
+ ConnectionChip,
946
+ {
947
+ connection: conn,
948
+ connectionData: connData,
949
+ onRemove: () => handleRemove(conn.connectionId)
950
+ },
951
+ conn.connectionId
952
+ );
953
+ }) }),
954
+ selectedConnections.map((conn) => /* @__PURE__ */ jsx(
955
+ ConnectionAgentUsage,
956
+ {
957
+ connectionId: conn.connectionId,
958
+ connectionName: selectedConnections.length > 1 ? getConnectionData(conn.connectionId)?.name : null
959
+ },
960
+ `agents-${conn.connectionId}`
961
+ )),
962
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
963
+ /* @__PURE__ */ jsxs(DropdownMenu, { open: isDropdownOpen, onOpenChange: setIsDropdownOpen, children: [
964
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
965
+ Button,
966
+ {
967
+ variant: "outline",
968
+ size: compact ? "sm" : "default",
969
+ className: "justify-between min-w-[200px]",
970
+ children: [
971
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
972
+ /* @__PURE__ */ jsx(
973
+ Icon,
974
+ {
975
+ icon: "mdi:link-variant",
976
+ className: "w-4 h-4 text-text-secondary"
977
+ }
978
+ ),
979
+ selectedConnections.length === 0 ? tr(
980
+ "fluidgrids.connections.connectionSelector.selectConnection",
981
+ "Select connection"
982
+ ) : multiple ? `${selectedConnections.length} selected` : tr(
983
+ "fluidgrids.connections.connectionSelector.changeConnection",
984
+ "Change connection"
985
+ )
986
+ ] }),
987
+ /* @__PURE__ */ jsx(
988
+ Icon,
989
+ {
990
+ icon: "mdi:chevron-down",
991
+ className: "w-4 h-4 text-text-secondary"
992
+ }
993
+ )
994
+ ]
995
+ }
996
+ ) }),
997
+ /* @__PURE__ */ jsxs(
998
+ DropdownMenuContent,
999
+ {
1000
+ align: "start",
1001
+ style: { zIndex: 1100 },
1002
+ className: "w-[280px] max-h-[400px] overflow-y-auto",
1003
+ children: [
1004
+ connectionsLoading ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-sm text-text-secondary", children: [
1005
+ /* @__PURE__ */ jsx(
1006
+ Icon,
1007
+ {
1008
+ icon: "mdi:loading",
1009
+ className: "w-6 h-6 mx-auto mb-2 animate-spin"
1010
+ }
1011
+ ),
1012
+ /* @__PURE__ */ jsx("p", { children: tr(
1013
+ "fluidgrids.connections.connectionSelector.loading",
1014
+ "Loading connections..."
1015
+ ) })
1016
+ ] }) : Object.keys(groupedConnections).length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-sm text-text-secondary", children: [
1017
+ /* @__PURE__ */ jsx(
1018
+ Icon,
1019
+ {
1020
+ icon: "mdi:link-off",
1021
+ className: "w-8 h-8 mx-auto mb-2 opacity-50"
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsx("p", { children: tr(
1025
+ "fluidgrids.connections.connectionSelector.emptyTitle",
1026
+ "No matching connections"
1027
+ ) }),
1028
+ /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: tr(
1029
+ "fluidgrids.connections.connectionSelector.emptyHint",
1030
+ "Create one in Integrations"
1031
+ ) })
1032
+ ] }) : Object.entries(groupedConnections).map(
1033
+ ([providerName, conns]) => /* @__PURE__ */ jsxs("div", { children: [
1034
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex items-center gap-2 text-xs font-semibold text-text-secondary uppercase tracking-wide", children: [
1035
+ /* @__PURE__ */ jsx(
1036
+ ProviderIcon,
1037
+ {
1038
+ variant: "bare",
1039
+ provider: conns[0]?.provider ?? {
1040
+ key: "generic",
1041
+ icon: void 0,
1042
+ category: "CUSTOM"
1043
+ },
1044
+ size: 16
1045
+ }
1046
+ ),
1047
+ providerName
1048
+ ] }),
1049
+ conns.map((conn) => {
1050
+ const isSelected = selectedConnections.some(
1051
+ (c) => c.connectionId === conn.id
1052
+ );
1053
+ const isActive = conn.status === "ACTIVE";
1054
+ return /* @__PURE__ */ jsxs(
1055
+ DropdownMenuItem,
1056
+ {
1057
+ onSelect: (event) => {
1058
+ if (multiple) event.preventDefault();
1059
+ handleSelect(conn);
1060
+ },
1061
+ className: `flex items-center justify-between cursor-pointer ${isSelected ? "bg-action-primary-bg/10" : ""} ${!isActive ? "opacity-60" : ""}`,
1062
+ children: [
1063
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0", children: [
1064
+ /* @__PURE__ */ jsxs("span", { className: "font-medium truncate flex items-center gap-1.5", children: [
1065
+ conn.name,
1066
+ showStatus && /* @__PURE__ */ jsx("span", { title: conn.status, children: /* @__PURE__ */ jsx(
1067
+ Icon,
1068
+ {
1069
+ icon: getStatusIcon(conn.status),
1070
+ className: `w-3.5 h-3.5 ${getStatusColor(conn.status)}`
1071
+ }
1072
+ ) })
1073
+ ] }),
1074
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: conn.externalAccountEmail || conn.updatedAt && `Updated ${formatAge(conn.updatedAt)}` })
1075
+ ] }),
1076
+ isSelected && /* @__PURE__ */ jsx(
1077
+ Icon,
1078
+ {
1079
+ icon: "mdi:check",
1080
+ className: "w-4 h-4 text-primary flex-shrink-0"
1081
+ }
1082
+ )
1083
+ ]
1084
+ },
1085
+ conn.id
1086
+ );
1087
+ }),
1088
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
1089
+ ] }, providerName)
1090
+ ),
1091
+ /* @__PURE__ */ jsx(
1092
+ SdkPermissionGate,
1093
+ {
1094
+ permission: FLUIDGRIDS_PERMISSIONS.INTEGRATION_CONNECTION_CREATE,
1095
+ children: /* @__PURE__ */ jsxs(
1096
+ DropdownMenuItem,
1097
+ {
1098
+ onClick: () => {
1099
+ handleNewConnection(newConnectionProviderKey(adapters));
1100
+ },
1101
+ className: "text-primary cursor-pointer",
1102
+ children: [
1103
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", className: "w-4 h-4 mr-2" }),
1104
+ tr(
1105
+ "fluidgrids.connections.connectionSelector.newConnection",
1106
+ "New connection"
1107
+ )
1108
+ ]
1109
+ }
1110
+ )
1111
+ }
1112
+ )
1113
+ ]
1114
+ }
1115
+ )
1116
+ ] }),
1117
+ selectedConnections.length === 0 && !compact && /* @__PURE__ */ jsx(
1118
+ SdkPermissionGate,
1119
+ {
1120
+ permission: FLUIDGRIDS_PERMISSIONS.INTEGRATION_CONNECTION_CREATE,
1121
+ children: /* @__PURE__ */ jsxs(
1122
+ Button,
1123
+ {
1124
+ variant: "ghost",
1125
+ size: "sm",
1126
+ onClick: () => handleNewConnection(newConnectionProviderKey(adapters)),
1127
+ className: "text-primary hover:text-primary/80",
1128
+ children: [
1129
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", className: "w-4 h-4 mr-1" }),
1130
+ tr("fluidgrids.connections.connectionSelector.new", "New")
1131
+ ]
1132
+ }
1133
+ )
1134
+ }
1135
+ )
1136
+ ] }),
1137
+ showStatus && showRequired && selectedConnections.length === 0 && adapters.some((adapter) => !isOptionalAdapter(adapter)) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-[var(--color-status-warning-text)]", children: [
1138
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:alert-circle", className: "w-3.5 h-3.5" }),
1139
+ /* @__PURE__ */ jsx("span", { children: tr(
1140
+ "fluidgrids.connections.connectionSelector.connectionRequired",
1141
+ "Connection required for this node"
1142
+ ) })
1143
+ ] }),
1144
+ /* @__PURE__ */ jsx(
1145
+ ConnectionFormDialog,
1146
+ {
1147
+ open: dialogOpen,
1148
+ onClose: () => setDialogOpen(false),
1149
+ providerKey: dialogProviderKey,
1150
+ onConnectionCreated: handleConnectionCreated
1151
+ }
1152
+ )
1153
+ ] });
1154
+ }
1155
+ function errorMessage(err) {
1156
+ if (err instanceof AgentFormError) return err.message;
1157
+ return describeOperationError(err);
1158
+ }
1159
+ function AgentEditor({
1160
+ initial,
1161
+ onCancel,
1162
+ onSave
1163
+ }) {
1164
+ const tr = useTr();
1165
+ const [form, setForm] = useState(initial);
1166
+ const [busy, setBusy] = useState(false);
1167
+ const { connections, loading: connectionsLoading } = useConnections();
1168
+ const setTool = (index, patch) => setForm((current) => ({
1169
+ ...current,
1170
+ tools: current.tools.map(
1171
+ (row, i) => i === index ? { ...row, ...patch } : row
1172
+ )
1173
+ }));
1174
+ const toggleConnection = (id, on) => setForm((current) => ({
1175
+ ...current,
1176
+ connectionScope: on ? [.../* @__PURE__ */ new Set([...current.connectionScope, id])] : current.connectionScope.filter((value) => value !== id)
1177
+ }));
1178
+ const submit = async () => {
1179
+ setBusy(true);
1180
+ try {
1181
+ await onSave(form);
1182
+ } catch (err) {
1183
+ toast.error(errorMessage(err));
1184
+ } finally {
1185
+ setBusy(false);
1186
+ }
1187
+ };
1188
+ return /* @__PURE__ */ jsxs(
1189
+ "div",
1190
+ {
1191
+ className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
1192
+ "data-testid": "agent-definition-editor",
1193
+ children: [
1194
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1195
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1196
+ /* @__PURE__ */ jsx(Label, { htmlFor: "agent-name", children: tr("fluidgrids.agents.name", "Name") }),
1197
+ /* @__PURE__ */ jsx(
1198
+ Input,
1199
+ {
1200
+ id: "agent-name",
1201
+ value: form.name,
1202
+ onChange: (e) => setForm({ ...form, name: e.target.value }),
1203
+ "data-testid": "agent-definition-name"
1204
+ }
1205
+ )
1206
+ ] }),
1207
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1208
+ /* @__PURE__ */ jsx(
1209
+ Switch,
1210
+ {
1211
+ id: "agent-active",
1212
+ checked: form.status === "ACTIVE",
1213
+ onCheckedChange: (on) => setForm({ ...form, status: on ? "ACTIVE" : "DISABLED" })
1214
+ }
1215
+ ),
1216
+ /* @__PURE__ */ jsx(Label, { htmlFor: "agent-active", children: tr("fluidgrids.agents.active", "Active") })
1217
+ ] })
1218
+ ] }),
1219
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1220
+ /* @__PURE__ */ jsx(Label, { htmlFor: "agent-description", children: tr("fluidgrids.agents.description", "Description") }),
1221
+ /* @__PURE__ */ jsx(
1222
+ Textarea,
1223
+ {
1224
+ id: "agent-description",
1225
+ value: form.description,
1226
+ onChange: (e) => setForm({ ...form, description: e.target.value }),
1227
+ rows: 2
1228
+ }
1229
+ )
1230
+ ] }),
1231
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1232
+ /* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.tools", "Tools the agent may call") }),
1233
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
1234
+ "fluidgrids.agents.toolsHelp",
1235
+ "A node type (for example store/slack) and the operations of it the agent may call, separated by commas. Leave the operations empty to allow every operation of that node type. Nothing else can be called."
1236
+ ) }),
1237
+ form.tools.map((row, index) => /* @__PURE__ */ jsxs(
1238
+ "div",
1239
+ {
1240
+ className: "flex flex-wrap items-center gap-2",
1241
+ "data-testid": `agent-definition-tool-${index}`,
1242
+ children: [
1243
+ /* @__PURE__ */ jsx(
1244
+ Input,
1245
+ {
1246
+ className: "w-48",
1247
+ placeholder: "store/slack",
1248
+ value: row.nodeType,
1249
+ onChange: (e) => setTool(index, { nodeType: e.target.value }),
1250
+ "aria-label": tr("fluidgrids.agents.nodeType", "Node type")
1251
+ }
1252
+ ),
1253
+ /* @__PURE__ */ jsx(
1254
+ Input,
1255
+ {
1256
+ className: "min-w-48 flex-1",
1257
+ placeholder: "postMessage, listChannels",
1258
+ value: row.operations,
1259
+ onChange: (e) => setTool(index, { operations: e.target.value }),
1260
+ "aria-label": tr("fluidgrids.agents.operations", "Operations")
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsx(
1264
+ Button$1,
1265
+ {
1266
+ variant: "ghost",
1267
+ size: "sm",
1268
+ onClick: () => setForm({
1269
+ ...form,
1270
+ tools: form.tools.filter((_, i) => i !== index)
1271
+ }),
1272
+ "aria-label": tr("fluidgrids.agents.removeTool", "Remove tool"),
1273
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", width: 16 })
1274
+ }
1275
+ )
1276
+ ]
1277
+ },
1278
+ index
1279
+ )),
1280
+ /* @__PURE__ */ jsxs(
1281
+ Button$1,
1282
+ {
1283
+ variant: "outline",
1284
+ size: "sm",
1285
+ onClick: () => setForm({
1286
+ ...form,
1287
+ tools: [...form.tools, { nodeType: "", operations: "" }]
1288
+ }),
1289
+ children: [
1290
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", width: 16 }),
1291
+ tr("fluidgrids.agents.addTool", "Add tool")
1292
+ ]
1293
+ }
1294
+ )
1295
+ ] }),
1296
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1297
+ /* @__PURE__ */ jsx(Label, { children: tr(
1298
+ "fluidgrids.agents.connections",
1299
+ "Connections the agent's tools may use"
1300
+ ) }),
1301
+ connectionsLoading ? /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr("fluidgrids.agents.loadingConnections", "Loading connections\u2026") }) : connections.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
1302
+ "fluidgrids.agents.noConnections",
1303
+ "This workspace has no connections yet."
1304
+ ) }) : /* @__PURE__ */ jsx("div", { className: "grid gap-1 sm:grid-cols-2", children: connections.map((connection) => /* @__PURE__ */ jsxs(
1305
+ "label",
1306
+ {
1307
+ className: "flex items-center gap-2 text-sm text-text-primary",
1308
+ children: [
1309
+ /* @__PURE__ */ jsx(
1310
+ Checkbox,
1311
+ {
1312
+ checked: form.connectionScope.includes(connection.id),
1313
+ onCheckedChange: (on) => toggleConnection(connection.id, on === true)
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsx("span", { children: connection.name }),
1317
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-text-tertiary", children: connection.provider?.name ?? connection.providerId })
1318
+ ]
1319
+ },
1320
+ connection.id
1321
+ )) })
1322
+ ] }),
1323
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1324
+ /* @__PURE__ */ jsx(
1325
+ Switch,
1326
+ {
1327
+ id: "agent-approval",
1328
+ checked: form.requireToolApproval,
1329
+ onCheckedChange: (on) => setForm({ ...form, requireToolApproval: on })
1330
+ }
1331
+ ),
1332
+ /* @__PURE__ */ jsx(Label, { htmlFor: "agent-approval", children: tr(
1333
+ "fluidgrids.agents.requireApproval",
1334
+ "Every tool call waits for a person's approval"
1335
+ ) })
1336
+ ] }),
1337
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1338
+ /* @__PURE__ */ jsx(
1339
+ Button$1,
1340
+ {
1341
+ onClick: () => void submit(),
1342
+ disabled: busy,
1343
+ "data-testid": "agent-definition-save",
1344
+ children: tr("fluidgrids.agents.save", "Save agent")
1345
+ }
1346
+ ),
1347
+ /* @__PURE__ */ jsx(Button$1, { variant: "outline", onClick: onCancel, disabled: busy, children: tr("fluidgrids.agents.cancel", "Cancel") })
1348
+ ] })
1349
+ ]
1350
+ }
1351
+ );
1352
+ }
1353
+ function AgentGrants({
1354
+ agent,
1355
+ loadGrants,
1356
+ onGrant,
1357
+ onRevoke
1358
+ }) {
1359
+ const tr = useTr();
1360
+ const [grants, setGrants] = useState(null);
1361
+ const [days, setDays] = useState(String(DEFAULT_GRANT_DAYS));
1362
+ const [busy, setBusy] = useState(false);
1363
+ const load = useCallback(async () => {
1364
+ try {
1365
+ setGrants(await loadGrants(agent.id));
1366
+ } catch (err) {
1367
+ setGrants([]);
1368
+ toast.error(errorMessage(err));
1369
+ }
1370
+ }, [agent.id, loadGrants]);
1371
+ useEffect(() => {
1372
+ void load();
1373
+ }, [load]);
1374
+ const run = async (action, done) => {
1375
+ setBusy(true);
1376
+ try {
1377
+ await action();
1378
+ toast.success(done);
1379
+ await load();
1380
+ } catch (err) {
1381
+ toast.error(errorMessage(err));
1382
+ } finally {
1383
+ setBusy(false);
1384
+ }
1385
+ };
1386
+ const mine = grants?.find(
1387
+ (grant) => grant.isMine && grant.status === "ACTIVE"
1388
+ );
1389
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `agent-grants-${agent.id}`, children: [
1390
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: mine ? /* @__PURE__ */ jsxs(Fragment, { children: [
1391
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-text-primary", children: mine.expiresAt ? tr(
1392
+ "fluidgrids.agents.myGrantUntil",
1393
+ "This agent may act for you until {{date}}.",
1394
+ { date: new Date(mine.expiresAt).toLocaleDateString() }
1395
+ ) : tr(
1396
+ "fluidgrids.agents.myGrant",
1397
+ "This agent may act for you."
1398
+ ) }),
1399
+ /* @__PURE__ */ jsx(
1400
+ Button$1,
1401
+ {
1402
+ variant: "outline",
1403
+ size: "sm",
1404
+ disabled: busy,
1405
+ onClick: () => void run(
1406
+ () => onRevoke(agent.id, null),
1407
+ tr("fluidgrids.agents.withdrawn", "Grant withdrawn")
1408
+ ),
1409
+ "data-testid": "agent-grant-withdraw",
1410
+ children: tr("fluidgrids.agents.withdraw", "Withdraw my grant")
1411
+ }
1412
+ )
1413
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1414
+ /* @__PURE__ */ jsx(Label, { htmlFor: `grant-days-${agent.id}`, className: "text-sm", children: tr("fluidgrids.agents.grantFor", "Let it act for me for") }),
1415
+ /* @__PURE__ */ jsx(
1416
+ Input,
1417
+ {
1418
+ id: `grant-days-${agent.id}`,
1419
+ className: "w-20",
1420
+ type: "number",
1421
+ min: 1,
1422
+ max: MAX_GRANT_DAYS,
1423
+ value: days,
1424
+ onChange: (e) => setDays(e.target.value)
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-text-secondary", children: tr("fluidgrids.agents.days", "days") }),
1428
+ /* @__PURE__ */ jsx(
1429
+ Button$1,
1430
+ {
1431
+ size: "sm",
1432
+ disabled: busy || agent.status !== "ACTIVE",
1433
+ onClick: () => void run(
1434
+ async () => onGrant(agent.id, grantDays(days)),
1435
+ tr("fluidgrids.agents.granted", "Grant given")
1436
+ ),
1437
+ "data-testid": "agent-grant-give",
1438
+ children: tr("fluidgrids.agents.grant", "Grant")
1439
+ }
1440
+ )
1441
+ ] }) }),
1442
+ grants && grants.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-xs text-text-secondary", children: grants.map((grant) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
1443
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: grant.userId }),
1444
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: grant.status }),
1445
+ grant.expiresAt ? /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.agents.until", "until {{date}}", {
1446
+ date: new Date(grant.expiresAt).toLocaleDateString()
1447
+ }) }) : null,
1448
+ !grant.isMine && grant.status === "ACTIVE" ? /* @__PURE__ */ jsx(
1449
+ Button$1,
1450
+ {
1451
+ variant: "ghost",
1452
+ size: "sm",
1453
+ disabled: busy,
1454
+ onClick: () => void run(
1455
+ () => onRevoke(agent.id, grant.userId),
1456
+ tr("fluidgrids.agents.revoked", "Grant revoked")
1457
+ ),
1458
+ children: tr("fluidgrids.agents.revoke", "Revoke")
1459
+ }
1460
+ ) : null
1461
+ ] }, grant.userId)) }) : null
1462
+ ] });
1463
+ }
1464
+ function AgentsTab() {
1465
+ const tr = useTr();
1466
+ const { agents, loading, error, save, remove, grants, grant, revoke } = useAgentDefinitions();
1467
+ const [editing, setEditing] = useState(null);
1468
+ const onSave = async (form) => {
1469
+ const input = formToInput(form);
1470
+ await save(editing?.id ?? null, input);
1471
+ toast.success(tr("fluidgrids.agents.saved", "Agent saved"));
1472
+ setEditing(null);
1473
+ };
1474
+ const onDelete = async (agent) => {
1475
+ try {
1476
+ await remove(agent.id);
1477
+ toast.success(tr("fluidgrids.agents.deleted", "Agent deleted"));
1478
+ } catch (err) {
1479
+ toast.error(errorMessage(err));
1480
+ }
1481
+ };
1482
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "data-testid": "agents-tab", children: [
1483
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [
1484
+ /* @__PURE__ */ jsxs("div", { children: [
1485
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.agents.title", "Agents") }),
1486
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
1487
+ "fluidgrids.agents.subtitle",
1488
+ "Give an AI Agent node an identity of its own. It can only call the tools and connections you allow, only for people who granted it, and every tool call is recorded under the agent and the person."
1489
+ ) })
1490
+ ] }),
1491
+ !editing ? /* @__PURE__ */ jsxs(
1492
+ Button$1,
1493
+ {
1494
+ onClick: () => setEditing({ id: null, form: emptyAgentForm() }),
1495
+ "data-testid": "agent-definition-new",
1496
+ children: [
1497
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", width: 16 }),
1498
+ tr("fluidgrids.agents.new", "New agent")
1499
+ ]
1500
+ }
1501
+ ) : null
1502
+ ] }),
1503
+ editing ? /* @__PURE__ */ jsx(
1504
+ AgentEditor,
1505
+ {
1506
+ initial: editing.form,
1507
+ onCancel: () => setEditing(null),
1508
+ onSave
1509
+ }
1510
+ ) : null,
1511
+ error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errorMessage(error) }) : null,
1512
+ loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.agents.loading", "Loading agents\u2026") }) : agents.length === 0 && !error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.agents.empty", "No agents yet.") }) : /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: agents.map((agent) => /* @__PURE__ */ jsxs(
1513
+ "li",
1514
+ {
1515
+ className: "rounded-lg border border-border-secondary bg-background-secondary p-3 space-y-2",
1516
+ "data-testid": `agent-definition-${agent.id}`,
1517
+ children: [
1518
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1519
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1520
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:robot-outline", width: 18 }),
1521
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-text-primary", children: agent.name }),
1522
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: agent.status }),
1523
+ agent.requireToolApproval ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: tr(
1524
+ "fluidgrids.agents.approvalBadge",
1525
+ "Approval required"
1526
+ ) }) : null
1527
+ ] }),
1528
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
1529
+ /* @__PURE__ */ jsx(
1530
+ Button$1,
1531
+ {
1532
+ variant: "ghost",
1533
+ size: "sm",
1534
+ onClick: () => setEditing({ id: agent.id, form: agentToForm(agent) }),
1535
+ children: tr("fluidgrids.agents.edit", "Edit")
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsx(
1539
+ Button$1,
1540
+ {
1541
+ variant: "ghost",
1542
+ size: "sm",
1543
+ onClick: () => void onDelete(agent),
1544
+ children: tr("fluidgrids.agents.delete", "Delete")
1545
+ }
1546
+ )
1547
+ ] })
1548
+ ] }),
1549
+ agent.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: agent.description }) : null,
1550
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-text-secondary", children: agent.toolAllowlist.length > 0 ? agent.toolAllowlist.map(describeRule).join(" \xB7 ") : tr("fluidgrids.agents.noTools", "No tools allowed") }),
1551
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-text-tertiary", children: tr(
1552
+ "fluidgrids.agents.connectionCount",
1553
+ "{{count}} connections in scope",
1554
+ { count: agent.connectionScope.length }
1555
+ ) }),
1556
+ /* @__PURE__ */ jsx(
1557
+ AgentGrants,
1558
+ {
1559
+ agent,
1560
+ loadGrants: grants,
1561
+ onGrant: grant,
1562
+ onRevoke: revoke
1563
+ }
1564
+ )
1565
+ ]
1566
+ },
1567
+ agent.id
1568
+ )) })
1569
+ ] });
1570
+ }
1571
+ var NONE = "__no_agent__";
1572
+ function AgentDefinitionSelectField({
1573
+ value,
1574
+ onChange,
1575
+ sectionLabelClassName,
1576
+ testIdPrefix = "ndv"
1577
+ }) {
1578
+ const tr = useTr();
1579
+ const { agents, loading } = useAgentDefinitionList();
1580
+ const selected = value ? agents.find((agent) => agent.id === value) : void 0;
1581
+ const missing = Boolean(value) && !loading && !selected;
1582
+ return /* @__PURE__ */ jsxs(
1583
+ "section",
1584
+ {
1585
+ className: "space-y-2",
1586
+ "data-testid": `${testIdPrefix}-agent-identity`,
1587
+ children: [
1588
+ /* @__PURE__ */ jsx("div", { className: sectionLabelClassName, children: tr("fluidgrids.builder.agentIdentity.label", "Agent identity") }),
1589
+ /* @__PURE__ */ jsxs(
1590
+ Select,
1591
+ {
1592
+ value: value ?? NONE,
1593
+ onValueChange: (next) => onChange(next === NONE ? null : next),
1594
+ children: [
1595
+ /* @__PURE__ */ jsx(
1596
+ SelectTrigger,
1597
+ {
1598
+ className: "w-full",
1599
+ "data-testid": `${testIdPrefix}-agent-identity-select`,
1600
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1601
+ }
1602
+ ),
1603
+ /* @__PURE__ */ jsxs(
1604
+ SelectContent,
1605
+ {
1606
+ style: { zIndex: 1100 },
1607
+ children: [
1608
+ /* @__PURE__ */ jsx(SelectItem, { value: NONE, children: tr(
1609
+ "fluidgrids.builder.agentIdentity.none",
1610
+ "None (tools run as the workflow)"
1611
+ ) }),
1612
+ agents.map((agent) => /* @__PURE__ */ jsx(
1613
+ SelectItem,
1614
+ {
1615
+ value: agent.id,
1616
+ disabled: agent.status !== "ACTIVE",
1617
+ children: agent.name
1618
+ },
1619
+ agent.id
1620
+ ))
1621
+ ]
1622
+ }
1623
+ )
1624
+ ]
1625
+ }
1626
+ ),
1627
+ selected ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: selected.toolAllowlist.length > 0 ? tr(
1628
+ "fluidgrids.builder.agentIdentity.allows",
1629
+ "Allowed tools: {{tools}}. Each tool call needs a grant from the person the run is attributed to.",
1630
+ { tools: selected.toolAllowlist.map(describeRule).join(" \xB7 ") }
1631
+ ) : tr(
1632
+ "fluidgrids.builder.agentIdentity.noTools",
1633
+ "This agent allows no tools."
1634
+ ) }) : null,
1635
+ missing ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: tr(
1636
+ "fluidgrids.builder.agentIdentity.missing",
1637
+ "The selected agent no longer exists in this workspace; saving will be refused."
1638
+ ) }) : null
1639
+ ]
1640
+ }
1641
+ );
1642
+ }
1643
+
1644
+ export { AgentDefinitionSelectField, AgentsTab, ConnectionFormDialog, ConnectionSelector, useConnection, useConnections, useConnectionsByProvider, useCreateConnection, useInitiateOAuth, useProviders };