@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.18

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 (119) hide show
  1. package/README.md +41 -0
  2. package/dist/AiStudioPage-BHVEU4Wp.js +774 -0
  3. package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
  4. package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
  5. package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
  6. package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
  7. package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
  8. package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
  9. package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
  10. package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
  11. package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
  12. package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
  13. package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
  14. package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
  15. package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
  16. package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
  17. package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
  18. package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
  19. package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
  20. package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
  21. package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
  22. package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
  23. package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
  24. package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
  25. package/dist/RunnersPage-CLCOCclc.js +467 -0
  26. package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CJLZvv4J.js} +743 -773
  27. package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
  28. package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
  29. package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
  30. package/dist/TemplateCard-DsC0KNH1.js +1416 -0
  31. package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
  32. package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
  33. package/dist/TemplatePreview-FayF6jxh.js +231 -0
  34. package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
  35. package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
  36. package/dist/VariablesPage-67WR_lVE.js +949 -0
  37. package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
  38. package/dist/WorkflowBuilder-CPnXEL5J.js +555 -0
  39. package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-YXRysvZc.js} +19 -585
  40. package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
  41. package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
  42. package/dist/WorkflowShareDialog-mbIwRQUY.js +168 -0
  43. package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
  44. package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-DiiGyZkT.js} +12 -6
  45. package/dist/agent-form-Bfvm32j1.js +83 -0
  46. package/dist/{agentTools-DJ5o2ekg.js → agentTools-8s_25Q_F.js} +3 -2
  47. package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
  48. package/dist/api-BrCHB5oA.js +224 -0
  49. package/dist/api-C8e0iV-Q.js +55 -0
  50. package/dist/button-DG7y_FVJ.js +48 -0
  51. package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-B6pYARgU.js} +2 -2
  52. package/dist/client-C9XSzSXr.js +1147 -0
  53. package/dist/code-editor-field-9-GdVE_8.js +111 -0
  54. package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
  55. package/dist/constants-B2lhWNoN.js +202 -0
  56. package/dist/constants-XdvUxeWK.d.ts +162 -0
  57. package/dist/dialog-Cjb4lB4D.js +105 -0
  58. package/dist/executions.d.ts +1 -1
  59. package/dist/executions.js +2 -1
  60. package/dist/feature-flags-DM5myWaD.js +296 -0
  61. package/dist/flags.d.ts +3 -0
  62. package/dist/flags.js +4 -0
  63. package/dist/{flow-builder-DEzT8TQu.js → flow-builder-CZDKRc6f.js} +662 -10716
  64. package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
  65. package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
  66. package/dist/history.d.ts +1 -1
  67. package/dist/history.js +1 -1
  68. package/dist/{index-DRf25-D3.d.ts → index-6JR2vD9J.d.ts} +33 -215
  69. package/dist/index-72XFgvbn.d.ts +132 -0
  70. package/dist/index-BcdtKBM-.d.ts +56 -0
  71. package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
  72. package/dist/index.d.ts +13 -9
  73. package/dist/index.js +52 -5300
  74. package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-CV4ZRwqh.js} +13 -8
  75. package/dist/node-editor-tab-body-DU-dh35X.js +174 -0
  76. package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
  77. package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
  78. package/dist/nodes.d.ts +1 -1
  79. package/dist/nodes.js +1 -1
  80. package/dist/observability.d.ts +1 -1
  81. package/dist/observability.js +2 -1
  82. package/dist/popover-BJHvrFGR.js +472 -0
  83. package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-ws3pLvbt.js} +3 -2
  84. package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
  85. package/dist/run-display-BycKYSU0.js +46 -0
  86. package/dist/run-list-links-ByYC0OlV.js +98 -0
  87. package/dist/schedule-panel-C0mfGQSD.js +7542 -0
  88. package/dist/settings.d.ts +2 -1
  89. package/dist/settings.js +2 -1
  90. package/dist/shared-CMfn30xT.js +196 -0
  91. package/dist/sidebar-node-editor-CR1PiZO-.js +177 -0
  92. package/dist/templates.d.ts +1 -1
  93. package/dist/templates.js +2 -1
  94. package/dist/theme-provider-B3mIuz2d.js +11 -0
  95. package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-B0dam3_o.js} +3 -397
  96. package/dist/useConnections-Cs7LCWEM.js +401 -0
  97. package/dist/useDurableParity-CzUQMFJ3.js +415 -0
  98. package/dist/useEntityTags-DhnsZcTl.js +236 -0
  99. package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
  100. package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
  101. package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
  102. package/dist/{WorkflowShareDialog-6RRDe-_v.js → useWorkflowCodeExport-Bu2QYJzA.js} +7 -210
  103. package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
  104. package/dist/utils-uaL5UVxb.js +65 -0
  105. package/dist/workflow-builder.d.ts +1 -1
  106. package/dist/workflow-builder.js +4 -2
  107. package/dist/workflow-host.d.ts +6 -0
  108. package/dist/workflow-host.js +9 -0
  109. package/dist/workflow-pages.d.ts +55 -0
  110. package/dist/workflow-pages.js +113 -0
  111. package/dist/workflows-list.d.ts +1 -1
  112. package/dist/workflows-list.js +1 -1
  113. package/package.json +13 -1
  114. package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
  115. package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
  116. package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
  117. package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
  118. package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
  119. package/dist/agent-form-BvMWhQ42.js +0 -496
@@ -1,15 +1,24 @@
1
1
  "use client";
2
- import { Bi as describeOperationError, Oi as getFluidGridsClient, Tt as ManagedAiWorkspacePolicyDocument, Ui as throwIfResultError, Vr as WorkflowNotificationSettingsDocument, ar as UpdateManagedAiWorkspacePolicyDocument, ir as UpdateDurableHistoryCapsDocument, lr as UpdateWorkflowNotificationSettingsDocument, qi as getFluidGridsConfig, tt as DurableHistoryCapsDocument } from "./node-endpoint-configs-DHFGA5MQ.js";
3
- import { At as useSaveStatusStore, Dn as FLUIDGRIDS_FLAGS, Fn as useFluidGridsFlag, Pn as useDurableParityEnabled, Qt as findSaveConflict, Rn as usePhaseCFlags, Zn as getGlobalStoreClient } from "./FluidGridsProvider-DmvFADFa.js";
4
- import { H as useFlowStore } from "./node-operation-selection--oy-yEq_.js";
2
+ import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
5
3
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
6
- import { Wt as useWorkflowDefinitionQuery, a as useConnections, n as useAgentDefinitions, qt as useWorkflowDefinitionsQuery, zt as useUpdateWorkflowDefinitionMutation } from "./useAgentDefinitions-NFtLytDR.js";
7
- import { KF as WorkflowSearchAttributeType, hI as FLUIDGRIDS_PERMISSIONS, xI as useFluidGridsPermission } from "./graphql-types-XcDLtFwU.js";
4
+ import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
5
+ import { n as useManagedAiPolicyAuthority } from "./ManagedAiPolicyAuthorityContext-CNkxIOZ1.js";
6
+ import { Br as WorkflowNotificationSettingsDocument, cr as UpdateWorkflowNotificationSettingsDocument, et as DurableHistoryCapsDocument, ir as UpdateManagedAiWorkspacePolicyDocument, rr as UpdateDurableHistoryCapsDocument, wt as ManagedAiWorkspacePolicyDocument } from "./graphql-operation-types-kX-MMjgW.js";
7
+ import { c as budgetInputValue, d as withBudgetMode, f as EffectBadge, m as useActionPolicies, n as checkCodecServerUrl, p as effectLabel, s as BUDGET_DIMENSIONS, u as withBudgetLimit } from "./codec-client-CJ95Rmqo.js";
8
+ import { _ as throwIfResultError, m as describeOperationError, n as getFluidGridsClient, x as getFluidGridsConfig } from "./client-C9XSzSXr.js";
9
+ import { c as useDurableParityEnabled, f as usePhaseCFlags, l as useFluidGridsFlag, t as FLUIDGRIDS_FLAGS } from "./feature-flags-DM5myWaD.js";
10
+ import { M as saveCodecServerSettings, _ as fetchPauseRules, a as createPauseRule, c as deletePauseRule, f as fetchArchiveSettings, g as fetchCodecServerSettings, j as saveArchiveSettings, l as deleteSearchAttribute, o as createSearchAttribute, r as clearArchiveSettings, x as fetchSearchAttributes } from "./api-BrCHB5oA.js";
11
+ import { i as NativeSelect, o as StatusPill, t as FieldError, u as useResource } from "./shared-CMfn30xT.js";
12
+ import { KF as WorkflowSearchAttributeType } from "./graphql-types-CDKVWH2R.js";
13
+ import { H as useFlowStore } from "./node-operation-selection--oy-yEq_.js";
14
+ import { At as useSaveStatusStore, Qt as findSaveConflict } from "./FluidGridsProvider-dvZSNtSW.js";
15
+ import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-CfEw3ZFD.js";
16
+ import { n as useConnections } from "./useConnections-Cs7LCWEM.js";
17
+ import { Nt as useUpdateWorkflowDefinitionMutation, Rt as useWorkflowDefinitionQuery, Vt as useWorkflowDefinitionsQuery, n as useAgentDefinitions } from "./useAgentDefinitions-B0dam3_o.js";
18
+ import { a as formToInput$2, i as emptyAgentForm, n as agentToForm, o as grantDays, r as describeRule, t as AgentFormError } from "./agent-form-Bfvm32j1.js";
19
+ import { i as getGlobalStoreClient } from "./node-registry-refresh-Dy0uRg9f.js";
8
20
  import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
9
- import { C as createSearchAttribute, E as deleteSearchAttribute, J as saveArchiveSettings, N as fetchCodecServerSettings, P as fetchPauseRules, R as fetchSearchAttributes, S as createPauseRule, T as deletePauseRule, Y as saveCodecServerSettings, _ as useResource, a as formToInput$2, b as clearArchiveSettings, c as FieldError, d as NativeSelect, i as emptyAgentForm, k as fetchArchiveSettings, n as agentToForm, o as grantDays, p as StatusPill, r as describeRule, t as AgentFormError } from "./agent-form-BvMWhQ42.js";
10
- import { c as budgetInputValue, d as withBudgetMode, f as EffectBadge, m as useActionPolicies, n as checkCodecServerUrl, p as effectLabel, s as BUDGET_DIMENSIONS, u as withBudgetLimit } from "./codec-client-DstqyzTN.js";
11
- import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-COCZ0hUS.js";
12
- import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
21
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
13
22
  import { useNavigate, useParams } from "react-router";
14
23
  import { Icon } from "@iconify/react";
15
24
  import { cn } from "@burdenoff/fe-libs/utils";
@@ -22,621 +31,190 @@ import { print } from "graphql";
22
31
  import { clearGraphqlFetchResponseCache, getStoredOrganizationId, getStoredProjectId, getWorkspaceTokenPayload, graphqlFetchPinned, isWorkspaceTokenValidForContext, prepareWorkspaceRequest } from "@burdenoff/fe-libs/shared";
23
32
  import { resolveExternalGatewayUrls } from "@burdenoff/fe-libs/shared/config";
24
33
 
25
- // src/modules/agent-definitions/components/AgentsTab.tsx
26
- /**
27
- * Settings → Agents (BOFF-9962): FluidGrids agents with their own identity.
28
- *
29
- * Builders create an agent, choose the tools it may call (node type and operations) and the
30
- * connections those tools may use, and can require approval for every tool call. Each person
31
- * lets the agent act for them with a time-boxed grant (30 days by default, 365 at most) and can
32
- * withdraw it at any time; admins can revoke anyone's grant. An AI Agent node that names the agent
33
- * runs its tools as "agent for user", and every call is recorded under both identities. Giving an
34
- * agent more access withdraws other people's grants until they grant again.
35
- */
36
- function errorMessage$2(err) {
37
- if (err instanceof AgentFormError) return err.message;
38
- return describeOperationError(err);
34
+ // src/modules/action-policies/utils/policy-form.ts
35
+ const POLICY_EFFECTS = [
36
+ "REQUIRE_APPROVAL",
37
+ "DENY",
38
+ "ALLOW",
39
+ "LOG"
40
+ ];
41
+ const POLICY_ENVIRONMENTS = [
42
+ "DEV",
43
+ "STAGING",
44
+ "PROD"
45
+ ];
46
+ const CONDITION_OPERATORS = [
47
+ {
48
+ value: "gt",
49
+ label: "is greater than",
50
+ kind: "number"
51
+ },
52
+ {
53
+ value: "gte",
54
+ label: "is at least",
55
+ kind: "number"
56
+ },
57
+ {
58
+ value: "lt",
59
+ label: "is less than",
60
+ kind: "number"
61
+ },
62
+ {
63
+ value: "lte",
64
+ label: "is at most",
65
+ kind: "number"
66
+ },
67
+ {
68
+ value: "eq",
69
+ label: "equals",
70
+ kind: "single"
71
+ },
72
+ {
73
+ value: "neq",
74
+ label: "does not equal",
75
+ kind: "single"
76
+ },
77
+ {
78
+ value: "in",
79
+ label: "is one of",
80
+ kind: "list"
81
+ },
82
+ {
83
+ value: "not_in",
84
+ label: "is not one of",
85
+ kind: "list"
86
+ },
87
+ {
88
+ value: "contains",
89
+ label: "contains",
90
+ kind: "single"
91
+ },
92
+ {
93
+ value: "count_gt",
94
+ label: "has more items than",
95
+ kind: "number"
96
+ },
97
+ {
98
+ value: "count_gte",
99
+ label: "has at least … items",
100
+ kind: "number"
101
+ },
102
+ {
103
+ value: "count_lt",
104
+ label: "has fewer items than",
105
+ kind: "number"
106
+ },
107
+ {
108
+ value: "count_lte",
109
+ label: "has at most … items",
110
+ kind: "number"
111
+ },
112
+ {
113
+ value: "exists",
114
+ label: "is set",
115
+ kind: "none"
116
+ },
117
+ {
118
+ value: "not_exists",
119
+ label: "is not set",
120
+ kind: "none"
121
+ }
122
+ ];
123
+ var PolicyFormError = class extends Error {};
124
+ const OPERATOR_KIND = new Map(CONDITION_OPERATORS.map((operator) => [operator.value, operator.kind]));
125
+ function operatorKind(op) {
126
+ return OPERATOR_KIND.get(op) ?? "single";
39
127
  }
40
- function AgentEditor({ initial, onCancel, onSave }) {
41
- const tr = useTr();
42
- const [form, setForm] = useState(initial);
43
- const [busy, setBusy] = useState(false);
44
- const { connections, loading: connectionsLoading } = useConnections();
45
- const setTool = (index, patch) => setForm((current) => ({
46
- ...current,
47
- tools: current.tools.map((row, i) => i === index ? {
48
- ...row,
49
- ...patch
50
- } : row)
51
- }));
52
- const toggleConnection = (id, on) => setForm((current) => ({
53
- ...current,
54
- connectionScope: on ? [.../* @__PURE__ */ new Set([...current.connectionScope, id])] : current.connectionScope.filter((value) => value !== id)
55
- }));
56
- const submit = async () => {
57
- setBusy(true);
58
- try {
59
- await onSave(form);
60
- } catch (err) {
61
- toast.error(errorMessage$2(err));
62
- } finally {
63
- setBusy(false);
128
+ function emptyPolicyForm() {
129
+ return {
130
+ name: "",
131
+ description: "",
132
+ enabled: true,
133
+ priority: "100",
134
+ effect: "REQUIRE_APPROVAL",
135
+ nodeTypes: "",
136
+ operations: "",
137
+ connectionProviders: "",
138
+ workflowTags: "",
139
+ environments: ["PROD"],
140
+ join: "all",
141
+ rows: []
142
+ };
143
+ }
144
+ function splitList(text) {
145
+ return [...new Set(text.split(",").map((item) => item.trim()).filter(Boolean))];
146
+ }
147
+ /** "1500" -> 1500, "true" -> true, "null" -> null, anything else stays text. */
148
+ function parseScalar(text) {
149
+ const trimmed = text.trim();
150
+ if (trimmed === "true") return true;
151
+ if (trimmed === "false") return false;
152
+ if (trimmed === "null") return null;
153
+ if (trimmed !== "" && Number.isFinite(Number(trimmed))) return Number(trimmed);
154
+ return trimmed;
155
+ }
156
+ function formatScalar(value) {
157
+ if (value === null) return "null";
158
+ return String(value);
159
+ }
160
+ function rowToLeaf(row, index) {
161
+ const path = row.path.trim();
162
+ if (!path) throw new PolicyFormError(`Condition ${index + 1} needs a parameter path`);
163
+ switch (operatorKind(row.op)) {
164
+ case "none": return {
165
+ path,
166
+ op: row.op
167
+ };
168
+ case "number": {
169
+ const value = Number(row.value.trim());
170
+ if (row.value.trim() === "" || !Number.isFinite(value)) throw new PolicyFormError(`Condition ${index + 1} needs a number`);
171
+ return {
172
+ path,
173
+ op: row.op,
174
+ value
175
+ };
176
+ }
177
+ case "list": {
178
+ const values = splitList(row.value).map(parseScalar);
179
+ if (values.length === 0) throw new PolicyFormError(`Condition ${index + 1} needs at least one value`);
180
+ return {
181
+ path,
182
+ op: row.op,
183
+ value: values
184
+ };
64
185
  }
65
- };
66
- return /* @__PURE__ */ jsxs("div", {
67
- className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
68
- "data-testid": "agent-definition-editor",
69
- children: [
70
- /* @__PURE__ */ jsxs("div", {
71
- className: "grid gap-3 sm:grid-cols-2",
72
- children: [/* @__PURE__ */ jsxs("div", {
73
- className: "space-y-1",
74
- children: [/* @__PURE__ */ jsx(Label, {
75
- htmlFor: "agent-name",
76
- children: tr("fluidgrids.agents.name", "Name")
77
- }), /* @__PURE__ */ jsx(Input, {
78
- id: "agent-name",
79
- value: form.name,
80
- onChange: (e) => setForm({
81
- ...form,
82
- name: e.target.value
83
- }),
84
- "data-testid": "agent-definition-name"
85
- })]
86
- }), /* @__PURE__ */ jsxs("div", {
87
- className: "flex items-end gap-2",
88
- children: [/* @__PURE__ */ jsx(Switch, {
89
- id: "agent-active",
90
- checked: form.status === "ACTIVE",
91
- onCheckedChange: (on) => setForm({
92
- ...form,
93
- status: on ? "ACTIVE" : "DISABLED"
94
- })
95
- }), /* @__PURE__ */ jsx(Label, {
96
- htmlFor: "agent-active",
97
- children: tr("fluidgrids.agents.active", "Active")
98
- })]
99
- })]
100
- }),
101
- /* @__PURE__ */ jsxs("div", {
102
- className: "space-y-1",
103
- children: [/* @__PURE__ */ jsx(Label, {
104
- htmlFor: "agent-description",
105
- children: tr("fluidgrids.agents.description", "Description")
106
- }), /* @__PURE__ */ jsx(Textarea, {
107
- id: "agent-description",
108
- value: form.description,
109
- onChange: (e) => setForm({
110
- ...form,
111
- description: e.target.value
112
- }),
113
- rows: 2
114
- })]
115
- }),
116
- /* @__PURE__ */ jsxs("div", {
117
- className: "space-y-2",
118
- children: [
119
- /* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.tools", "Tools the agent may call") }),
120
- /* @__PURE__ */ jsx("p", {
121
- className: "text-xs text-text-secondary",
122
- children: tr("fluidgrids.agents.toolsHelp", "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.")
123
- }),
124
- form.tools.map((row, index) => /* @__PURE__ */ jsxs("div", {
125
- className: "flex flex-wrap items-center gap-2",
126
- "data-testid": `agent-definition-tool-${index}`,
127
- children: [
128
- /* @__PURE__ */ jsx(Input, {
129
- className: "w-48",
130
- placeholder: "store/slack",
131
- value: row.nodeType,
132
- onChange: (e) => setTool(index, { nodeType: e.target.value }),
133
- "aria-label": tr("fluidgrids.agents.nodeType", "Node type")
134
- }),
135
- /* @__PURE__ */ jsx(Input, {
136
- className: "min-w-48 flex-1",
137
- placeholder: "postMessage, listChannels",
138
- value: row.operations,
139
- onChange: (e) => setTool(index, { operations: e.target.value }),
140
- "aria-label": tr("fluidgrids.agents.operations", "Operations")
141
- }),
142
- /* @__PURE__ */ jsx(Button$1, {
143
- variant: "ghost",
144
- size: "sm",
145
- onClick: () => setForm({
146
- ...form,
147
- tools: form.tools.filter((_, i) => i !== index)
148
- }),
149
- "aria-label": tr("fluidgrids.agents.removeTool", "Remove tool"),
150
- children: /* @__PURE__ */ jsx(Icon, {
151
- icon: "mdi:close",
152
- width: 16
153
- })
154
- })
155
- ]
156
- }, index)),
157
- /* @__PURE__ */ jsxs(Button$1, {
158
- variant: "outline",
159
- size: "sm",
160
- onClick: () => setForm({
161
- ...form,
162
- tools: [...form.tools, {
163
- nodeType: "",
164
- operations: ""
165
- }]
166
- }),
167
- children: [/* @__PURE__ */ jsx(Icon, {
168
- icon: "mdi:plus",
169
- width: 16
170
- }), tr("fluidgrids.agents.addTool", "Add tool")]
171
- })
172
- ]
173
- }),
174
- /* @__PURE__ */ jsxs("div", {
175
- className: "space-y-2",
176
- children: [/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.connections", "Connections the agent's tools may use") }), connectionsLoading ? /* @__PURE__ */ jsx("p", {
177
- className: "text-xs text-text-secondary",
178
- children: tr("fluidgrids.agents.loadingConnections", "Loading connections…")
179
- }) : connections.length === 0 ? /* @__PURE__ */ jsx("p", {
180
- className: "text-xs text-text-secondary",
181
- children: tr("fluidgrids.agents.noConnections", "This workspace has no connections yet.")
182
- }) : /* @__PURE__ */ jsx("div", {
183
- className: "grid gap-1 sm:grid-cols-2",
184
- children: connections.map((connection) => /* @__PURE__ */ jsxs("label", {
185
- className: "flex items-center gap-2 text-sm text-text-primary",
186
- children: [
187
- /* @__PURE__ */ jsx(Checkbox, {
188
- checked: form.connectionScope.includes(connection.id),
189
- onCheckedChange: (on) => toggleConnection(connection.id, on === true)
190
- }),
191
- /* @__PURE__ */ jsx("span", { children: connection.name }),
192
- /* @__PURE__ */ jsx("span", {
193
- className: "text-xs text-text-tertiary",
194
- children: connection.provider?.name ?? connection.providerId
195
- })
196
- ]
197
- }, connection.id))
198
- })]
199
- }),
200
- /* @__PURE__ */ jsxs("div", {
201
- className: "flex items-center gap-2",
202
- children: [/* @__PURE__ */ jsx(Switch, {
203
- id: "agent-approval",
204
- checked: form.requireToolApproval,
205
- onCheckedChange: (on) => setForm({
206
- ...form,
207
- requireToolApproval: on
208
- })
209
- }), /* @__PURE__ */ jsx(Label, {
210
- htmlFor: "agent-approval",
211
- children: tr("fluidgrids.agents.requireApproval", "Every tool call waits for a person's approval")
212
- })]
213
- }),
214
- /* @__PURE__ */ jsxs("div", {
215
- className: "flex gap-2",
216
- children: [/* @__PURE__ */ jsx(Button$1, {
217
- onClick: () => void submit(),
218
- disabled: busy,
219
- "data-testid": "agent-definition-save",
220
- children: tr("fluidgrids.agents.save", "Save agent")
221
- }), /* @__PURE__ */ jsx(Button$1, {
222
- variant: "outline",
223
- onClick: onCancel,
224
- disabled: busy,
225
- children: tr("fluidgrids.agents.cancel", "Cancel")
226
- })]
227
- })
228
- ]
229
- });
230
- }
231
- function AgentGrants({ agent, loadGrants, onGrant, onRevoke }) {
232
- const tr = useTr();
233
- const [grants, setGrants] = useState(null);
234
- const [days, setDays] = useState(String(30));
235
- const [busy, setBusy] = useState(false);
236
- const load = useCallback(async () => {
237
- try {
238
- setGrants(await loadGrants(agent.id));
239
- } catch (err) {
240
- setGrants([]);
241
- toast.error(errorMessage$2(err));
242
- }
243
- }, [agent.id, loadGrants]);
244
- useEffect(() => {
245
- load();
246
- }, [load]);
247
- const run = async (action, done) => {
248
- setBusy(true);
249
- try {
250
- await action();
251
- toast.success(done);
252
- await load();
253
- } catch (err) {
254
- toast.error(errorMessage$2(err));
255
- } finally {
256
- setBusy(false);
257
- }
258
- };
259
- const mine = grants?.find((grant) => grant.isMine && grant.status === "ACTIVE");
260
- return /* @__PURE__ */ jsxs("div", {
261
- className: "space-y-2",
262
- "data-testid": `agent-grants-${agent.id}`,
263
- children: [/* @__PURE__ */ jsx("div", {
264
- className: "flex flex-wrap items-center gap-2",
265
- children: mine ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
266
- className: "text-sm text-text-primary",
267
- children: mine.expiresAt ? tr("fluidgrids.agents.myGrantUntil", "This agent may act for you until {{date}}.", { date: new Date(mine.expiresAt).toLocaleDateString() }) : tr("fluidgrids.agents.myGrant", "This agent may act for you.")
268
- }), /* @__PURE__ */ jsx(Button$1, {
269
- variant: "outline",
270
- size: "sm",
271
- disabled: busy,
272
- onClick: () => void run(() => onRevoke(agent.id, null), tr("fluidgrids.agents.withdrawn", "Grant withdrawn")),
273
- "data-testid": "agent-grant-withdraw",
274
- children: tr("fluidgrids.agents.withdraw", "Withdraw my grant")
275
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
276
- /* @__PURE__ */ jsx(Label, {
277
- htmlFor: `grant-days-${agent.id}`,
278
- className: "text-sm",
279
- children: tr("fluidgrids.agents.grantFor", "Let it act for me for")
280
- }),
281
- /* @__PURE__ */ jsx(Input, {
282
- id: `grant-days-${agent.id}`,
283
- className: "w-20",
284
- type: "number",
285
- min: 1,
286
- max: 365,
287
- value: days,
288
- onChange: (e) => setDays(e.target.value)
289
- }),
290
- /* @__PURE__ */ jsx("span", {
291
- className: "text-sm text-text-secondary",
292
- children: tr("fluidgrids.agents.days", "days")
293
- }),
294
- /* @__PURE__ */ jsx(Button$1, {
295
- size: "sm",
296
- disabled: busy || agent.status !== "ACTIVE",
297
- onClick: () => void run(async () => onGrant(agent.id, grantDays(days)), tr("fluidgrids.agents.granted", "Grant given")),
298
- "data-testid": "agent-grant-give",
299
- children: tr("fluidgrids.agents.grant", "Grant")
300
- })
301
- ] })
302
- }), grants && grants.length > 0 ? /* @__PURE__ */ jsx("ul", {
303
- className: "space-y-1 text-xs text-text-secondary",
304
- children: grants.map((grant) => /* @__PURE__ */ jsxs("li", {
305
- className: "flex items-center gap-2",
306
- children: [
307
- /* @__PURE__ */ jsx("span", {
308
- className: "font-mono",
309
- children: grant.userId
310
- }),
311
- /* @__PURE__ */ jsx(Badge, {
312
- variant: "outline",
313
- children: grant.status
314
- }),
315
- grant.expiresAt ? /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.agents.until", "until {{date}}", { date: new Date(grant.expiresAt).toLocaleDateString() }) }) : null,
316
- !grant.isMine && grant.status === "ACTIVE" ? /* @__PURE__ */ jsx(Button$1, {
317
- variant: "ghost",
318
- size: "sm",
319
- disabled: busy,
320
- onClick: () => void run(() => onRevoke(agent.id, grant.userId), tr("fluidgrids.agents.revoked", "Grant revoked")),
321
- children: tr("fluidgrids.agents.revoke", "Revoke")
322
- }) : null
323
- ]
324
- }, grant.userId))
325
- }) : null]
326
- });
327
- }
328
- function AgentsTab() {
329
- const tr = useTr();
330
- const { agents, loading, error, save, remove, grants, grant, revoke } = useAgentDefinitions();
331
- const [editing, setEditing] = useState(null);
332
- const onSave = async (form) => {
333
- const input = formToInput$2(form);
334
- await save(editing?.id ?? null, input);
335
- toast.success(tr("fluidgrids.agents.saved", "Agent saved"));
336
- setEditing(null);
337
- };
338
- const onDelete = async (agent) => {
339
- try {
340
- await remove(agent.id);
341
- toast.success(tr("fluidgrids.agents.deleted", "Agent deleted"));
342
- } catch (err) {
343
- toast.error(errorMessage$2(err));
344
- }
345
- };
346
- return /* @__PURE__ */ jsxs("div", {
347
- className: "space-y-4",
348
- "data-testid": "agents-tab",
349
- children: [
350
- /* @__PURE__ */ jsxs("div", {
351
- className: "flex flex-wrap items-start justify-between gap-2",
352
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
353
- className: "text-base font-semibold text-text-primary",
354
- children: tr("fluidgrids.agents.title", "Agents")
355
- }), /* @__PURE__ */ jsx("p", {
356
- className: "text-sm text-text-secondary",
357
- children: tr("fluidgrids.agents.subtitle", "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.")
358
- })] }), !editing ? /* @__PURE__ */ jsxs(Button$1, {
359
- onClick: () => setEditing({
360
- id: null,
361
- form: emptyAgentForm()
362
- }),
363
- "data-testid": "agent-definition-new",
364
- children: [/* @__PURE__ */ jsx(Icon, {
365
- icon: "mdi:plus",
366
- width: 16
367
- }), tr("fluidgrids.agents.new", "New agent")]
368
- }) : null]
369
- }),
370
- editing ? /* @__PURE__ */ jsx(AgentEditor, {
371
- initial: editing.form,
372
- onCancel: () => setEditing(null),
373
- onSave
374
- }) : null,
375
- error ? /* @__PURE__ */ jsx("p", {
376
- className: "text-sm text-destructive",
377
- children: errorMessage$2(error)
378
- }) : null,
379
- loading ? /* @__PURE__ */ jsx("p", {
380
- className: "text-sm text-text-secondary",
381
- children: tr("fluidgrids.agents.loading", "Loading agents…")
382
- }) : agents.length === 0 && !error ? /* @__PURE__ */ jsx("p", {
383
- className: "text-sm text-text-secondary",
384
- children: tr("fluidgrids.agents.empty", "No agents yet.")
385
- }) : /* @__PURE__ */ jsx("ul", {
386
- className: "space-y-3",
387
- children: agents.map((agent) => /* @__PURE__ */ jsxs("li", {
388
- className: "rounded-lg border border-border-secondary bg-background-secondary p-3 space-y-2",
389
- "data-testid": `agent-definition-${agent.id}`,
390
- children: [
391
- /* @__PURE__ */ jsxs("div", {
392
- className: "flex flex-wrap items-center justify-between gap-2",
393
- children: [/* @__PURE__ */ jsxs("div", {
394
- className: "flex items-center gap-2",
395
- children: [
396
- /* @__PURE__ */ jsx(Icon, {
397
- icon: "mdi:robot-outline",
398
- width: 18
399
- }),
400
- /* @__PURE__ */ jsx("span", {
401
- className: "font-medium text-text-primary",
402
- children: agent.name
403
- }),
404
- /* @__PURE__ */ jsx(Badge, {
405
- variant: "outline",
406
- children: agent.status
407
- }),
408
- agent.requireToolApproval ? /* @__PURE__ */ jsx(Badge, {
409
- variant: "outline",
410
- children: tr("fluidgrids.agents.approvalBadge", "Approval required")
411
- }) : null
412
- ]
413
- }), /* @__PURE__ */ jsxs("div", {
414
- className: "flex gap-1",
415
- children: [/* @__PURE__ */ jsx(Button$1, {
416
- variant: "ghost",
417
- size: "sm",
418
- onClick: () => setEditing({
419
- id: agent.id,
420
- form: agentToForm(agent)
421
- }),
422
- children: tr("fluidgrids.agents.edit", "Edit")
423
- }), /* @__PURE__ */ jsx(Button$1, {
424
- variant: "ghost",
425
- size: "sm",
426
- onClick: () => void onDelete(agent),
427
- children: tr("fluidgrids.agents.delete", "Delete")
428
- })]
429
- })]
430
- }),
431
- agent.description ? /* @__PURE__ */ jsx("p", {
432
- className: "text-sm text-text-secondary",
433
- children: agent.description
434
- }) : null,
435
- /* @__PURE__ */ jsx("div", {
436
- className: "text-xs text-text-secondary",
437
- children: agent.toolAllowlist.length > 0 ? agent.toolAllowlist.map(describeRule).join(" · ") : tr("fluidgrids.agents.noTools", "No tools allowed")
438
- }),
439
- /* @__PURE__ */ jsx("div", {
440
- className: "text-xs text-text-tertiary",
441
- children: tr("fluidgrids.agents.connectionCount", "{{count}} connections in scope", { count: agent.connectionScope.length })
442
- }),
443
- /* @__PURE__ */ jsx(AgentGrants, {
444
- agent,
445
- loadGrants: grants,
446
- onGrant: grant,
447
- onRevoke: revoke
448
- })
449
- ]
450
- }, agent.id))
451
- })
452
- ]
453
- });
454
- }
455
-
456
- // src/modules/action-policies/utils/policy-form.ts
457
- const POLICY_EFFECTS = [
458
- "REQUIRE_APPROVAL",
459
- "DENY",
460
- "ALLOW",
461
- "LOG"
462
- ];
463
- const POLICY_ENVIRONMENTS = [
464
- "DEV",
465
- "STAGING",
466
- "PROD"
467
- ];
468
- const CONDITION_OPERATORS = [
469
- {
470
- value: "gt",
471
- label: "is greater than",
472
- kind: "number"
473
- },
474
- {
475
- value: "gte",
476
- label: "is at least",
477
- kind: "number"
478
- },
479
- {
480
- value: "lt",
481
- label: "is less than",
482
- kind: "number"
483
- },
484
- {
485
- value: "lte",
486
- label: "is at most",
487
- kind: "number"
488
- },
489
- {
490
- value: "eq",
491
- label: "equals",
492
- kind: "single"
493
- },
494
- {
495
- value: "neq",
496
- label: "does not equal",
497
- kind: "single"
498
- },
499
- {
500
- value: "in",
501
- label: "is one of",
502
- kind: "list"
503
- },
504
- {
505
- value: "not_in",
506
- label: "is not one of",
507
- kind: "list"
508
- },
509
- {
510
- value: "contains",
511
- label: "contains",
512
- kind: "single"
513
- },
514
- {
515
- value: "count_gt",
516
- label: "has more items than",
517
- kind: "number"
518
- },
519
- {
520
- value: "count_gte",
521
- label: "has at least … items",
522
- kind: "number"
523
- },
524
- {
525
- value: "count_lt",
526
- label: "has fewer items than",
527
- kind: "number"
528
- },
529
- {
530
- value: "count_lte",
531
- label: "has at most … items",
532
- kind: "number"
533
- },
534
- {
535
- value: "exists",
536
- label: "is set",
537
- kind: "none"
538
- },
539
- {
540
- value: "not_exists",
541
- label: "is not set",
542
- kind: "none"
543
- }
544
- ];
545
- var PolicyFormError = class extends Error {};
546
- const OPERATOR_KIND = new Map(CONDITION_OPERATORS.map((operator) => [operator.value, operator.kind]));
547
- function operatorKind(op) {
548
- return OPERATOR_KIND.get(op) ?? "single";
549
- }
550
- function emptyPolicyForm() {
551
- return {
552
- name: "",
553
- description: "",
554
- enabled: true,
555
- priority: "100",
556
- effect: "REQUIRE_APPROVAL",
557
- nodeTypes: "",
558
- operations: "",
559
- connectionProviders: "",
560
- workflowTags: "",
561
- environments: ["PROD"],
562
- join: "all",
563
- rows: []
564
- };
565
- }
566
- function splitList(text) {
567
- return [...new Set(text.split(",").map((item) => item.trim()).filter(Boolean))];
568
- }
569
- /** "1500" -> 1500, "true" -> true, "null" -> null, anything else stays text. */
570
- function parseScalar(text) {
571
- const trimmed = text.trim();
572
- if (trimmed === "true") return true;
573
- if (trimmed === "false") return false;
574
- if (trimmed === "null") return null;
575
- if (trimmed !== "" && Number.isFinite(Number(trimmed))) return Number(trimmed);
576
- return trimmed;
577
- }
578
- function formatScalar(value) {
579
- if (value === null) return "null";
580
- return String(value);
581
- }
582
- function rowToLeaf(row, index) {
583
- const path = row.path.trim();
584
- if (!path) throw new PolicyFormError(`Condition ${index + 1} needs a parameter path`);
585
- switch (operatorKind(row.op)) {
586
- case "none": return {
587
- path,
588
- op: row.op
589
- };
590
- case "number": {
591
- const value = Number(row.value.trim());
592
- if (row.value.trim() === "" || !Number.isFinite(value)) throw new PolicyFormError(`Condition ${index + 1} needs a number`);
593
- return {
594
- path,
595
- op: row.op,
596
- value
597
- };
598
- }
599
- case "list": {
600
- const values = splitList(row.value).map(parseScalar);
601
- if (values.length === 0) throw new PolicyFormError(`Condition ${index + 1} needs at least one value`);
602
- return {
603
- path,
604
- op: row.op,
605
- value: values
606
- };
607
- }
608
- default: return {
609
- path,
610
- op: row.op,
611
- value: parseScalar(row.value)
612
- };
613
- }
614
- }
615
- /** The editor state as the API input. Throws PolicyFormError with a readable reason. */
616
- function formToInput$1(form) {
617
- const name = form.name.trim();
618
- if (!name) throw new PolicyFormError("Give the policy a name");
619
- const priority = Number(form.priority);
620
- if (!Number.isInteger(priority) || priority < 0 || priority > 1e5) throw new PolicyFormError("Priority is a whole number from 0 to 100000");
621
- const leaves = form.rows.map(rowToLeaf);
622
- let condition = null;
623
- if (leaves.length === 1) condition = leaves[0];
624
- else if (leaves.length > 1) condition = { [form.join]: leaves };
625
- return {
626
- ...form.id ? { id: form.id } : {},
627
- name,
628
- description: form.description.trim() || null,
629
- enabled: form.enabled,
630
- priority,
631
- effect: form.effect,
632
- matcher: {
633
- nodeTypes: splitList(form.nodeTypes),
634
- operations: splitList(form.operations),
635
- connectionProviders: splitList(form.connectionProviders),
636
- workflowTags: splitList(form.workflowTags)
637
- },
638
- condition,
639
- environments: form.environments
186
+ default: return {
187
+ path,
188
+ op: row.op,
189
+ value: parseScalar(row.value)
190
+ };
191
+ }
192
+ }
193
+ /** The editor state as the API input. Throws PolicyFormError with a readable reason. */
194
+ function formToInput$1(form) {
195
+ const name = form.name.trim();
196
+ if (!name) throw new PolicyFormError("Give the policy a name");
197
+ const priority = Number(form.priority);
198
+ if (!Number.isInteger(priority) || priority < 0 || priority > 1e5) throw new PolicyFormError("Priority is a whole number from 0 to 100000");
199
+ const leaves = form.rows.map(rowToLeaf);
200
+ let condition = null;
201
+ if (leaves.length === 1) condition = leaves[0];
202
+ else if (leaves.length > 1) condition = { [form.join]: leaves };
203
+ return {
204
+ ...form.id ? { id: form.id } : {},
205
+ name,
206
+ description: form.description.trim() || null,
207
+ enabled: form.enabled,
208
+ priority,
209
+ effect: form.effect,
210
+ matcher: {
211
+ nodeTypes: splitList(form.nodeTypes),
212
+ operations: splitList(form.operations),
213
+ connectionProviders: splitList(form.connectionProviders),
214
+ workflowTags: splitList(form.workflowTags)
215
+ },
216
+ condition,
217
+ environments: form.environments
640
218
  };
641
219
  }
642
220
  function isRecord$1(value) {
@@ -736,7 +314,7 @@ function needsAllowFallback(draft, others) {
736
314
  * rule; changing a policy or deciding an approval needs workflow publish rights.
737
315
  */
738
316
  const FIELD$1 = "rounded-md border border-border-secondary bg-background-secondary px-2 py-1.5 text-sm text-text-primary";
739
- function errorMessage$1(err) {
317
+ function errorMessage$2(err) {
740
318
  if (err instanceof PolicyFormError) return err.message;
741
319
  return describeOperationError(err);
742
320
  }
@@ -850,7 +428,7 @@ function PolicyEditor({ initial, conditionEditable, storedCondition, others, onC
850
428
  try {
851
429
  await action();
852
430
  } catch (err) {
853
- toast.error(errorMessage$1(err));
431
+ toast.error(errorMessage$2(err));
854
432
  } finally {
855
433
  setBusy(false);
856
434
  }
@@ -1205,7 +783,7 @@ function ApprovalsList({ approvals, onDecide }) {
1205
783
  await onDecide(approval.id, true);
1206
784
  toast.success(tr("fluidgrids.actionPolicies.approved", "Approved — the run continues"));
1207
785
  } catch (err) {
1208
- toast.error(errorMessage$1(err));
786
+ toast.error(errorMessage$2(err));
1209
787
  } finally {
1210
788
  setBusy(null);
1211
789
  }
@@ -1221,7 +799,7 @@ function ApprovalsList({ approvals, onDecide }) {
1221
799
  await onDecide(approval.id, false);
1222
800
  toast.success(tr("fluidgrids.actionPolicies.declined", "Declined — the node fails"));
1223
801
  } catch (err) {
1224
- toast.error(errorMessage$1(err));
802
+ toast.error(errorMessage$2(err));
1225
803
  } finally {
1226
804
  setBusy(null);
1227
805
  }
@@ -1348,7 +926,7 @@ function ActionPoliciesTab() {
1348
926
  try {
1349
927
  setPreview(await dryRun({ excludePolicyId: policy.id }));
1350
928
  } catch (err) {
1351
- toast.error(errorMessage$1(err));
929
+ toast.error(errorMessage$2(err));
1352
930
  }
1353
931
  },
1354
932
  children: tr("fluidgrids.actionPolicies.withoutIt", "Without it")
@@ -1381,7 +959,7 @@ function ActionPoliciesTab() {
1381
959
  await remove(policy.id);
1382
960
  toast.success(tr("fluidgrids.actionPolicies.deleted", "Policy deleted"));
1383
961
  } catch (err) {
1384
- toast.error(errorMessage$1(err));
962
+ toast.error(errorMessage$2(err));
1385
963
  }
1386
964
  },
1387
965
  children: confirmDelete === policy.id ? tr("fluidgrids.actionPolicies.confirmDelete", "Confirm delete") : tr("fluidgrids.actionPolicies.delete", "Delete")
@@ -1394,12 +972,6 @@ function ActionPoliciesTab() {
1394
972
  });
1395
973
  }
1396
974
 
1397
- // src/modules/settings/api/ManagedAiPolicyAuthorityContext.ts
1398
- const ManagedAiAuthorityContext = createContext(null);
1399
- function useManagedAiPolicyAuthority() {
1400
- return useContext(ManagedAiAuthorityContext);
1401
- }
1402
-
1403
975
  // src/modules/settings/api/managed-ai-request.ts
1404
976
  const canonical = {
1405
977
  prepare: prepareWorkspaceRequest,
@@ -3805,127 +3377,558 @@ function LogStreamingSettings() {
3805
3377
  className: "size-4 rounded border-border-secondary accent-action-primary-bg"
3806
3378
  }), tr("fluidgrids.settings.logStreaming.enabled", "Destination enabled")]
3807
3379
  }),
3808
- /* @__PURE__ */ jsxs("div", {
3809
- className: "flex justify-end gap-2",
3810
- children: [/* @__PURE__ */ jsx(Button$1, {
3811
- variant: "outline",
3812
- onClick: resetForm,
3813
- children: tr("fluidgrids.settings.logStreaming.cancel", "Cancel")
3814
- }), /* @__PURE__ */ jsx(Button$1, {
3815
- "data-agent": "confirm",
3816
- onClick: () => void save(),
3817
- disabled: actions.loading,
3818
- children: actions.loading ? tr("fluidgrids.settings.logStreaming.saving", "Saving…") : tr("fluidgrids.settings.logStreaming.save", "Save destination")
3819
- })]
3380
+ /* @__PURE__ */ jsxs("div", {
3381
+ className: "flex justify-end gap-2",
3382
+ children: [/* @__PURE__ */ jsx(Button$1, {
3383
+ variant: "outline",
3384
+ onClick: resetForm,
3385
+ children: tr("fluidgrids.settings.logStreaming.cancel", "Cancel")
3386
+ }), /* @__PURE__ */ jsx(Button$1, {
3387
+ "data-agent": "confirm",
3388
+ onClick: () => void save(),
3389
+ disabled: actions.loading,
3390
+ children: actions.loading ? tr("fluidgrids.settings.logStreaming.saving", "Saving…") : tr("fluidgrids.settings.logStreaming.save", "Save destination")
3391
+ })]
3392
+ })
3393
+ ]
3394
+ })]
3395
+ }),
3396
+ /* @__PURE__ */ jsxs("div", {
3397
+ className: "space-y-3",
3398
+ children: [
3399
+ loading && destinations.length === 0 && /* @__PURE__ */ jsx("div", {
3400
+ className: "rounded-lg border border-border-secondary bg-background-secondary p-6 text-sm text-text-secondary",
3401
+ children: tr("fluidgrids.settings.logStreaming.loading", "Loading destinations…")
3402
+ }),
3403
+ !loading && destinations.length === 0 && /* @__PURE__ */ jsxs("div", {
3404
+ className: "rounded-lg border border-dashed border-border-secondary bg-background-secondary p-8 text-center",
3405
+ children: [
3406
+ /* @__PURE__ */ jsx(Icon, {
3407
+ icon: "mdi:transit-connection-variant",
3408
+ className: "mx-auto mb-3 text-text-secondary",
3409
+ width: 32
3410
+ }),
3411
+ /* @__PURE__ */ jsx("p", {
3412
+ className: "font-medium text-text-primary",
3413
+ children: tr("fluidgrids.settings.logStreaming.emptyTitle", "No log destinations yet")
3414
+ }),
3415
+ /* @__PURE__ */ jsx("p", {
3416
+ className: "mt-1 text-sm text-text-secondary",
3417
+ children: tr("fluidgrids.settings.logStreaming.emptyDescription", "Add a webhook, syslog target, or Sentry DSN to start streaming events.")
3418
+ })
3419
+ ]
3420
+ }),
3421
+ destinations.map((destination) => /* @__PURE__ */ jsx(Card, {
3422
+ className: "border-border-secondary bg-background-secondary",
3423
+ children: /* @__PURE__ */ jsxs(CardContent, {
3424
+ className: "flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between",
3425
+ children: [/* @__PURE__ */ jsxs("div", {
3426
+ className: "min-w-0",
3427
+ children: [
3428
+ /* @__PURE__ */ jsxs("div", {
3429
+ className: "flex flex-wrap items-center gap-2",
3430
+ children: [
3431
+ /* @__PURE__ */ jsx("h3", {
3432
+ className: "font-medium text-text-primary",
3433
+ children: destination.name
3434
+ }),
3435
+ /* @__PURE__ */ jsx("span", {
3436
+ className: "rounded-full bg-background-tertiary px-2 py-0.5 text-xs text-text-secondary",
3437
+ children: destination.provider
3438
+ }),
3439
+ /* @__PURE__ */ jsx("span", {
3440
+ className: destination.isEnabled ? "text-xs text-status-success-text" : "text-xs text-text-secondary",
3441
+ children: destination.isEnabled ? tr("fluidgrids.settings.logStreaming.enabledBadge", "Enabled") : tr("fluidgrids.settings.logStreaming.disabledBadge", "Disabled")
3442
+ })
3443
+ ]
3444
+ }),
3445
+ /* @__PURE__ */ jsx("p", {
3446
+ className: "mt-1 truncate text-sm text-text-secondary",
3447
+ children: destination.endpoint
3448
+ }),
3449
+ /* @__PURE__ */ jsxs("p", {
3450
+ className: "mt-1 text-xs text-text-secondary",
3451
+ children: [destination.eventFilters.length > 0 ? destination.eventFilters.map(eventLabel).join(", ") : tr("fluidgrids.settings.logStreaming.allEvents", "All events"), destination.hasSecret && " · Secret configured"]
3452
+ }),
3453
+ destination.lastDispatchError && /* @__PURE__ */ jsx("p", {
3454
+ className: "mt-2 text-xs text-status-error-text",
3455
+ children: destination.lastDispatchError
3456
+ })
3457
+ ]
3458
+ }), /* @__PURE__ */ jsxs("div", {
3459
+ className: "flex shrink-0 flex-wrap gap-2",
3460
+ children: [
3461
+ /* @__PURE__ */ jsxs(Button$1, {
3462
+ variant: "outline",
3463
+ size: "sm",
3464
+ onClick: () => void sendTest(destination),
3465
+ disabled: actions.loading,
3466
+ children: [/* @__PURE__ */ jsx(Icon, {
3467
+ icon: "mdi:send-check-outline",
3468
+ className: "mr-1",
3469
+ width: 15
3470
+ }), tr("fluidgrids.settings.logStreaming.test", "Send test")]
3471
+ }),
3472
+ /* @__PURE__ */ jsx(Button$1, {
3473
+ variant: "outline",
3474
+ size: "sm",
3475
+ onClick: () => {
3476
+ setEditingId(destination.id);
3477
+ setShowForm(true);
3478
+ },
3479
+ disabled: actions.loading,
3480
+ children: tr("fluidgrids.settings.logStreaming.editButton", "Edit")
3481
+ }),
3482
+ /* @__PURE__ */ jsxs(Button$1, {
3483
+ variant: "ghost",
3484
+ size: "sm",
3485
+ onClick: () => void remove(destination),
3486
+ disabled: actions.loading,
3487
+ children: [/* @__PURE__ */ jsx(Icon, {
3488
+ icon: "mdi:trash-can-outline",
3489
+ className: "text-status-error-text",
3490
+ width: 17
3491
+ }), /* @__PURE__ */ jsx("span", {
3492
+ className: "sr-only",
3493
+ children: tr("fluidgrids.settings.logStreaming.delete", "Delete")
3494
+ })]
3495
+ })
3496
+ ]
3497
+ })]
3498
+ })
3499
+ }, destination.id))
3500
+ ]
3501
+ })
3502
+ ]
3503
+ });
3504
+ }
3505
+
3506
+ // src/modules/agent-definitions/components/AgentsTab.tsx
3507
+ /**
3508
+ * Settings → Agents (BOFF-9962): FluidGrids agents with their own identity.
3509
+ *
3510
+ * Builders create an agent, choose the tools it may call (node type and operations) and the
3511
+ * connections those tools may use, and can require approval for every tool call. Each person
3512
+ * lets the agent act for them with a time-boxed grant (30 days by default, 365 at most) and can
3513
+ * withdraw it at any time; admins can revoke anyone's grant. An AI Agent node that names the agent
3514
+ * runs its tools as "agent for user", and every call is recorded under both identities. Giving an
3515
+ * agent more access withdraws other people's grants until they grant again.
3516
+ */
3517
+ function errorMessage$1(err) {
3518
+ if (err instanceof AgentFormError) return err.message;
3519
+ return describeOperationError(err);
3520
+ }
3521
+ function AgentEditor({ initial, onCancel, onSave }) {
3522
+ const tr = useTr();
3523
+ const [form, setForm] = useState(initial);
3524
+ const [busy, setBusy] = useState(false);
3525
+ const { connections, loading: connectionsLoading } = useConnections();
3526
+ const setTool = (index, patch) => setForm((current) => ({
3527
+ ...current,
3528
+ tools: current.tools.map((row, i) => i === index ? {
3529
+ ...row,
3530
+ ...patch
3531
+ } : row)
3532
+ }));
3533
+ const toggleConnection = (id, on) => setForm((current) => ({
3534
+ ...current,
3535
+ connectionScope: on ? [.../* @__PURE__ */ new Set([...current.connectionScope, id])] : current.connectionScope.filter((value) => value !== id)
3536
+ }));
3537
+ const submit = async () => {
3538
+ setBusy(true);
3539
+ try {
3540
+ await onSave(form);
3541
+ } catch (err) {
3542
+ toast.error(errorMessage$1(err));
3543
+ } finally {
3544
+ setBusy(false);
3545
+ }
3546
+ };
3547
+ return /* @__PURE__ */ jsxs("div", {
3548
+ className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
3549
+ "data-testid": "agent-definition-editor",
3550
+ children: [
3551
+ /* @__PURE__ */ jsxs("div", {
3552
+ className: "grid gap-3 sm:grid-cols-2",
3553
+ children: [/* @__PURE__ */ jsxs("div", {
3554
+ className: "space-y-1",
3555
+ children: [/* @__PURE__ */ jsx(Label, {
3556
+ htmlFor: "agent-name",
3557
+ children: tr("fluidgrids.agents.name", "Name")
3558
+ }), /* @__PURE__ */ jsx(Input, {
3559
+ id: "agent-name",
3560
+ value: form.name,
3561
+ onChange: (e) => setForm({
3562
+ ...form,
3563
+ name: e.target.value
3564
+ }),
3565
+ "data-testid": "agent-definition-name"
3566
+ })]
3567
+ }), /* @__PURE__ */ jsxs("div", {
3568
+ className: "flex items-end gap-2",
3569
+ children: [/* @__PURE__ */ jsx(Switch, {
3570
+ id: "agent-active",
3571
+ checked: form.status === "ACTIVE",
3572
+ onCheckedChange: (on) => setForm({
3573
+ ...form,
3574
+ status: on ? "ACTIVE" : "DISABLED"
3820
3575
  })
3821
- ]
3576
+ }), /* @__PURE__ */ jsx(Label, {
3577
+ htmlFor: "agent-active",
3578
+ children: tr("fluidgrids.agents.active", "Active")
3579
+ })]
3822
3580
  })]
3823
3581
  }),
3824
3582
  /* @__PURE__ */ jsxs("div", {
3825
- className: "space-y-3",
3583
+ className: "space-y-1",
3584
+ children: [/* @__PURE__ */ jsx(Label, {
3585
+ htmlFor: "agent-description",
3586
+ children: tr("fluidgrids.agents.description", "Description")
3587
+ }), /* @__PURE__ */ jsx(Textarea, {
3588
+ id: "agent-description",
3589
+ value: form.description,
3590
+ onChange: (e) => setForm({
3591
+ ...form,
3592
+ description: e.target.value
3593
+ }),
3594
+ rows: 2
3595
+ })]
3596
+ }),
3597
+ /* @__PURE__ */ jsxs("div", {
3598
+ className: "space-y-2",
3599
+ children: [
3600
+ /* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.tools", "Tools the agent may call") }),
3601
+ /* @__PURE__ */ jsx("p", {
3602
+ className: "text-xs text-text-secondary",
3603
+ children: tr("fluidgrids.agents.toolsHelp", "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.")
3604
+ }),
3605
+ form.tools.map((row, index) => /* @__PURE__ */ jsxs("div", {
3606
+ className: "flex flex-wrap items-center gap-2",
3607
+ "data-testid": `agent-definition-tool-${index}`,
3608
+ children: [
3609
+ /* @__PURE__ */ jsx(Input, {
3610
+ className: "w-48",
3611
+ placeholder: "store/slack",
3612
+ value: row.nodeType,
3613
+ onChange: (e) => setTool(index, { nodeType: e.target.value }),
3614
+ "aria-label": tr("fluidgrids.agents.nodeType", "Node type")
3615
+ }),
3616
+ /* @__PURE__ */ jsx(Input, {
3617
+ className: "min-w-48 flex-1",
3618
+ placeholder: "postMessage, listChannels",
3619
+ value: row.operations,
3620
+ onChange: (e) => setTool(index, { operations: e.target.value }),
3621
+ "aria-label": tr("fluidgrids.agents.operations", "Operations")
3622
+ }),
3623
+ /* @__PURE__ */ jsx(Button$1, {
3624
+ variant: "ghost",
3625
+ size: "sm",
3626
+ onClick: () => setForm({
3627
+ ...form,
3628
+ tools: form.tools.filter((_, i) => i !== index)
3629
+ }),
3630
+ "aria-label": tr("fluidgrids.agents.removeTool", "Remove tool"),
3631
+ children: /* @__PURE__ */ jsx(Icon, {
3632
+ icon: "mdi:close",
3633
+ width: 16
3634
+ })
3635
+ })
3636
+ ]
3637
+ }, index)),
3638
+ /* @__PURE__ */ jsxs(Button$1, {
3639
+ variant: "outline",
3640
+ size: "sm",
3641
+ onClick: () => setForm({
3642
+ ...form,
3643
+ tools: [...form.tools, {
3644
+ nodeType: "",
3645
+ operations: ""
3646
+ }]
3647
+ }),
3648
+ children: [/* @__PURE__ */ jsx(Icon, {
3649
+ icon: "mdi:plus",
3650
+ width: 16
3651
+ }), tr("fluidgrids.agents.addTool", "Add tool")]
3652
+ })
3653
+ ]
3654
+ }),
3655
+ /* @__PURE__ */ jsxs("div", {
3656
+ className: "space-y-2",
3657
+ children: [/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.connections", "Connections the agent's tools may use") }), connectionsLoading ? /* @__PURE__ */ jsx("p", {
3658
+ className: "text-xs text-text-secondary",
3659
+ children: tr("fluidgrids.agents.loadingConnections", "Loading connections…")
3660
+ }) : connections.length === 0 ? /* @__PURE__ */ jsx("p", {
3661
+ className: "text-xs text-text-secondary",
3662
+ children: tr("fluidgrids.agents.noConnections", "This workspace has no connections yet.")
3663
+ }) : /* @__PURE__ */ jsx("div", {
3664
+ className: "grid gap-1 sm:grid-cols-2",
3665
+ children: connections.map((connection) => /* @__PURE__ */ jsxs("label", {
3666
+ className: "flex items-center gap-2 text-sm text-text-primary",
3667
+ children: [
3668
+ /* @__PURE__ */ jsx(Checkbox, {
3669
+ checked: form.connectionScope.includes(connection.id),
3670
+ onCheckedChange: (on) => toggleConnection(connection.id, on === true)
3671
+ }),
3672
+ /* @__PURE__ */ jsx("span", { children: connection.name }),
3673
+ /* @__PURE__ */ jsx("span", {
3674
+ className: "text-xs text-text-tertiary",
3675
+ children: connection.provider?.name ?? connection.providerId
3676
+ })
3677
+ ]
3678
+ }, connection.id))
3679
+ })]
3680
+ }),
3681
+ /* @__PURE__ */ jsxs("div", {
3682
+ className: "flex items-center gap-2",
3683
+ children: [/* @__PURE__ */ jsx(Switch, {
3684
+ id: "agent-approval",
3685
+ checked: form.requireToolApproval,
3686
+ onCheckedChange: (on) => setForm({
3687
+ ...form,
3688
+ requireToolApproval: on
3689
+ })
3690
+ }), /* @__PURE__ */ jsx(Label, {
3691
+ htmlFor: "agent-approval",
3692
+ children: tr("fluidgrids.agents.requireApproval", "Every tool call waits for a person's approval")
3693
+ })]
3694
+ }),
3695
+ /* @__PURE__ */ jsxs("div", {
3696
+ className: "flex gap-2",
3697
+ children: [/* @__PURE__ */ jsx(Button$1, {
3698
+ onClick: () => void submit(),
3699
+ disabled: busy,
3700
+ "data-testid": "agent-definition-save",
3701
+ children: tr("fluidgrids.agents.save", "Save agent")
3702
+ }), /* @__PURE__ */ jsx(Button$1, {
3703
+ variant: "outline",
3704
+ onClick: onCancel,
3705
+ disabled: busy,
3706
+ children: tr("fluidgrids.agents.cancel", "Cancel")
3707
+ })]
3708
+ })
3709
+ ]
3710
+ });
3711
+ }
3712
+ function AgentGrants({ agent, loadGrants, onGrant, onRevoke }) {
3713
+ const tr = useTr();
3714
+ const [grants, setGrants] = useState(null);
3715
+ const [days, setDays] = useState(String(30));
3716
+ const [busy, setBusy] = useState(false);
3717
+ const load = useCallback(async () => {
3718
+ try {
3719
+ setGrants(await loadGrants(agent.id));
3720
+ } catch (err) {
3721
+ setGrants([]);
3722
+ toast.error(errorMessage$1(err));
3723
+ }
3724
+ }, [agent.id, loadGrants]);
3725
+ useEffect(() => {
3726
+ load();
3727
+ }, [load]);
3728
+ const run = async (action, done) => {
3729
+ setBusy(true);
3730
+ try {
3731
+ await action();
3732
+ toast.success(done);
3733
+ await load();
3734
+ } catch (err) {
3735
+ toast.error(errorMessage$1(err));
3736
+ } finally {
3737
+ setBusy(false);
3738
+ }
3739
+ };
3740
+ const mine = grants?.find((grant) => grant.isMine && grant.status === "ACTIVE");
3741
+ return /* @__PURE__ */ jsxs("div", {
3742
+ className: "space-y-2",
3743
+ "data-testid": `agent-grants-${agent.id}`,
3744
+ children: [/* @__PURE__ */ jsx("div", {
3745
+ className: "flex flex-wrap items-center gap-2",
3746
+ children: mine ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
3747
+ className: "text-sm text-text-primary",
3748
+ children: mine.expiresAt ? tr("fluidgrids.agents.myGrantUntil", "This agent may act for you until {{date}}.", { date: new Date(mine.expiresAt).toLocaleDateString() }) : tr("fluidgrids.agents.myGrant", "This agent may act for you.")
3749
+ }), /* @__PURE__ */ jsx(Button$1, {
3750
+ variant: "outline",
3751
+ size: "sm",
3752
+ disabled: busy,
3753
+ onClick: () => void run(() => onRevoke(agent.id, null), tr("fluidgrids.agents.withdrawn", "Grant withdrawn")),
3754
+ "data-testid": "agent-grant-withdraw",
3755
+ children: tr("fluidgrids.agents.withdraw", "Withdraw my grant")
3756
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3757
+ /* @__PURE__ */ jsx(Label, {
3758
+ htmlFor: `grant-days-${agent.id}`,
3759
+ className: "text-sm",
3760
+ children: tr("fluidgrids.agents.grantFor", "Let it act for me for")
3761
+ }),
3762
+ /* @__PURE__ */ jsx(Input, {
3763
+ id: `grant-days-${agent.id}`,
3764
+ className: "w-20",
3765
+ type: "number",
3766
+ min: 1,
3767
+ max: 365,
3768
+ value: days,
3769
+ onChange: (e) => setDays(e.target.value)
3770
+ }),
3771
+ /* @__PURE__ */ jsx("span", {
3772
+ className: "text-sm text-text-secondary",
3773
+ children: tr("fluidgrids.agents.days", "days")
3774
+ }),
3775
+ /* @__PURE__ */ jsx(Button$1, {
3776
+ size: "sm",
3777
+ disabled: busy || agent.status !== "ACTIVE",
3778
+ onClick: () => void run(async () => onGrant(agent.id, grantDays(days)), tr("fluidgrids.agents.granted", "Grant given")),
3779
+ "data-testid": "agent-grant-give",
3780
+ children: tr("fluidgrids.agents.grant", "Grant")
3781
+ })
3782
+ ] })
3783
+ }), grants && grants.length > 0 ? /* @__PURE__ */ jsx("ul", {
3784
+ className: "space-y-1 text-xs text-text-secondary",
3785
+ children: grants.map((grant) => /* @__PURE__ */ jsxs("li", {
3786
+ className: "flex items-center gap-2",
3826
3787
  children: [
3827
- loading && destinations.length === 0 && /* @__PURE__ */ jsx("div", {
3828
- className: "rounded-lg border border-border-secondary bg-background-secondary p-6 text-sm text-text-secondary",
3829
- children: tr("fluidgrids.settings.logStreaming.loading", "Loading destinations…")
3788
+ /* @__PURE__ */ jsx("span", {
3789
+ className: "font-mono",
3790
+ children: grant.userId
3830
3791
  }),
3831
- !loading && destinations.length === 0 && /* @__PURE__ */ jsxs("div", {
3832
- className: "rounded-lg border border-dashed border-border-secondary bg-background-secondary p-8 text-center",
3833
- children: [
3834
- /* @__PURE__ */ jsx(Icon, {
3835
- icon: "mdi:transit-connection-variant",
3836
- className: "mx-auto mb-3 text-text-secondary",
3837
- width: 32
3838
- }),
3839
- /* @__PURE__ */ jsx("p", {
3840
- className: "font-medium text-text-primary",
3841
- children: tr("fluidgrids.settings.logStreaming.emptyTitle", "No log destinations yet")
3842
- }),
3843
- /* @__PURE__ */ jsx("p", {
3844
- className: "mt-1 text-sm text-text-secondary",
3845
- children: tr("fluidgrids.settings.logStreaming.emptyDescription", "Add a webhook, syslog target, or Sentry DSN to start streaming events.")
3846
- })
3847
- ]
3792
+ /* @__PURE__ */ jsx(Badge, {
3793
+ variant: "outline",
3794
+ children: grant.status
3848
3795
  }),
3849
- destinations.map((destination) => /* @__PURE__ */ jsx(Card, {
3850
- className: "border-border-secondary bg-background-secondary",
3851
- children: /* @__PURE__ */ jsxs(CardContent, {
3852
- className: "flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between",
3796
+ grant.expiresAt ? /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.agents.until", "until {{date}}", { date: new Date(grant.expiresAt).toLocaleDateString() }) }) : null,
3797
+ !grant.isMine && grant.status === "ACTIVE" ? /* @__PURE__ */ jsx(Button$1, {
3798
+ variant: "ghost",
3799
+ size: "sm",
3800
+ disabled: busy,
3801
+ onClick: () => void run(() => onRevoke(agent.id, grant.userId), tr("fluidgrids.agents.revoked", "Grant revoked")),
3802
+ children: tr("fluidgrids.agents.revoke", "Revoke")
3803
+ }) : null
3804
+ ]
3805
+ }, grant.userId))
3806
+ }) : null]
3807
+ });
3808
+ }
3809
+ function AgentsTab() {
3810
+ const tr = useTr();
3811
+ const { agents, loading, error, save, remove, grants, grant, revoke } = useAgentDefinitions();
3812
+ const [editing, setEditing] = useState(null);
3813
+ const onSave = async (form) => {
3814
+ const input = formToInput$2(form);
3815
+ await save(editing?.id ?? null, input);
3816
+ toast.success(tr("fluidgrids.agents.saved", "Agent saved"));
3817
+ setEditing(null);
3818
+ };
3819
+ const onDelete = async (agent) => {
3820
+ try {
3821
+ await remove(agent.id);
3822
+ toast.success(tr("fluidgrids.agents.deleted", "Agent deleted"));
3823
+ } catch (err) {
3824
+ toast.error(errorMessage$1(err));
3825
+ }
3826
+ };
3827
+ return /* @__PURE__ */ jsxs("div", {
3828
+ className: "space-y-4",
3829
+ "data-testid": "agents-tab",
3830
+ children: [
3831
+ /* @__PURE__ */ jsxs("div", {
3832
+ className: "flex flex-wrap items-start justify-between gap-2",
3833
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
3834
+ className: "text-base font-semibold text-text-primary",
3835
+ children: tr("fluidgrids.agents.title", "Agents")
3836
+ }), /* @__PURE__ */ jsx("p", {
3837
+ className: "text-sm text-text-secondary",
3838
+ children: tr("fluidgrids.agents.subtitle", "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.")
3839
+ })] }), !editing ? /* @__PURE__ */ jsxs(Button$1, {
3840
+ onClick: () => setEditing({
3841
+ id: null,
3842
+ form: emptyAgentForm()
3843
+ }),
3844
+ "data-testid": "agent-definition-new",
3845
+ children: [/* @__PURE__ */ jsx(Icon, {
3846
+ icon: "mdi:plus",
3847
+ width: 16
3848
+ }), tr("fluidgrids.agents.new", "New agent")]
3849
+ }) : null]
3850
+ }),
3851
+ editing ? /* @__PURE__ */ jsx(AgentEditor, {
3852
+ initial: editing.form,
3853
+ onCancel: () => setEditing(null),
3854
+ onSave
3855
+ }) : null,
3856
+ error ? /* @__PURE__ */ jsx("p", {
3857
+ className: "text-sm text-destructive",
3858
+ children: errorMessage$1(error)
3859
+ }) : null,
3860
+ loading ? /* @__PURE__ */ jsx("p", {
3861
+ className: "text-sm text-text-secondary",
3862
+ children: tr("fluidgrids.agents.loading", "Loading agents…")
3863
+ }) : agents.length === 0 && !error ? /* @__PURE__ */ jsx("p", {
3864
+ className: "text-sm text-text-secondary",
3865
+ children: tr("fluidgrids.agents.empty", "No agents yet.")
3866
+ }) : /* @__PURE__ */ jsx("ul", {
3867
+ className: "space-y-3",
3868
+ children: agents.map((agent) => /* @__PURE__ */ jsxs("li", {
3869
+ className: "rounded-lg border border-border-secondary bg-background-secondary p-3 space-y-2",
3870
+ "data-testid": `agent-definition-${agent.id}`,
3871
+ children: [
3872
+ /* @__PURE__ */ jsxs("div", {
3873
+ className: "flex flex-wrap items-center justify-between gap-2",
3853
3874
  children: [/* @__PURE__ */ jsxs("div", {
3854
- className: "min-w-0",
3875
+ className: "flex items-center gap-2",
3855
3876
  children: [
3856
- /* @__PURE__ */ jsxs("div", {
3857
- className: "flex flex-wrap items-center gap-2",
3858
- children: [
3859
- /* @__PURE__ */ jsx("h3", {
3860
- className: "font-medium text-text-primary",
3861
- children: destination.name
3862
- }),
3863
- /* @__PURE__ */ jsx("span", {
3864
- className: "rounded-full bg-background-tertiary px-2 py-0.5 text-xs text-text-secondary",
3865
- children: destination.provider
3866
- }),
3867
- /* @__PURE__ */ jsx("span", {
3868
- className: destination.isEnabled ? "text-xs text-status-success-text" : "text-xs text-text-secondary",
3869
- children: destination.isEnabled ? tr("fluidgrids.settings.logStreaming.enabledBadge", "Enabled") : tr("fluidgrids.settings.logStreaming.disabledBadge", "Disabled")
3870
- })
3871
- ]
3872
- }),
3873
- /* @__PURE__ */ jsx("p", {
3874
- className: "mt-1 truncate text-sm text-text-secondary",
3875
- children: destination.endpoint
3877
+ /* @__PURE__ */ jsx(Icon, {
3878
+ icon: "mdi:robot-outline",
3879
+ width: 18
3876
3880
  }),
3877
- /* @__PURE__ */ jsxs("p", {
3878
- className: "mt-1 text-xs text-text-secondary",
3879
- children: [destination.eventFilters.length > 0 ? destination.eventFilters.map(eventLabel).join(", ") : tr("fluidgrids.settings.logStreaming.allEvents", "All events"), destination.hasSecret && " · Secret configured"]
3881
+ /* @__PURE__ */ jsx("span", {
3882
+ className: "font-medium text-text-primary",
3883
+ children: agent.name
3880
3884
  }),
3881
- destination.lastDispatchError && /* @__PURE__ */ jsx("p", {
3882
- className: "mt-2 text-xs text-status-error-text",
3883
- children: destination.lastDispatchError
3884
- })
3885
- ]
3886
- }), /* @__PURE__ */ jsxs("div", {
3887
- className: "flex shrink-0 flex-wrap gap-2",
3888
- children: [
3889
- /* @__PURE__ */ jsxs(Button$1, {
3885
+ /* @__PURE__ */ jsx(Badge, {
3890
3886
  variant: "outline",
3891
- size: "sm",
3892
- onClick: () => void sendTest(destination),
3893
- disabled: actions.loading,
3894
- children: [/* @__PURE__ */ jsx(Icon, {
3895
- icon: "mdi:send-check-outline",
3896
- className: "mr-1",
3897
- width: 15
3898
- }), tr("fluidgrids.settings.logStreaming.test", "Send test")]
3887
+ children: agent.status
3899
3888
  }),
3900
- /* @__PURE__ */ jsx(Button$1, {
3889
+ agent.requireToolApproval ? /* @__PURE__ */ jsx(Badge, {
3901
3890
  variant: "outline",
3902
- size: "sm",
3903
- onClick: () => {
3904
- setEditingId(destination.id);
3905
- setShowForm(true);
3906
- },
3907
- disabled: actions.loading,
3908
- children: tr("fluidgrids.settings.logStreaming.editButton", "Edit")
3909
- }),
3910
- /* @__PURE__ */ jsxs(Button$1, {
3911
- variant: "ghost",
3912
- size: "sm",
3913
- onClick: () => void remove(destination),
3914
- disabled: actions.loading,
3915
- children: [/* @__PURE__ */ jsx(Icon, {
3916
- icon: "mdi:trash-can-outline",
3917
- className: "text-status-error-text",
3918
- width: 17
3919
- }), /* @__PURE__ */ jsx("span", {
3920
- className: "sr-only",
3921
- children: tr("fluidgrids.settings.logStreaming.delete", "Delete")
3922
- })]
3923
- })
3891
+ children: tr("fluidgrids.agents.approvalBadge", "Approval required")
3892
+ }) : null
3924
3893
  ]
3894
+ }), /* @__PURE__ */ jsxs("div", {
3895
+ className: "flex gap-1",
3896
+ children: [/* @__PURE__ */ jsx(Button$1, {
3897
+ variant: "ghost",
3898
+ size: "sm",
3899
+ onClick: () => setEditing({
3900
+ id: agent.id,
3901
+ form: agentToForm(agent)
3902
+ }),
3903
+ children: tr("fluidgrids.agents.edit", "Edit")
3904
+ }), /* @__PURE__ */ jsx(Button$1, {
3905
+ variant: "ghost",
3906
+ size: "sm",
3907
+ onClick: () => void onDelete(agent),
3908
+ children: tr("fluidgrids.agents.delete", "Delete")
3909
+ })]
3925
3910
  })]
3911
+ }),
3912
+ agent.description ? /* @__PURE__ */ jsx("p", {
3913
+ className: "text-sm text-text-secondary",
3914
+ children: agent.description
3915
+ }) : null,
3916
+ /* @__PURE__ */ jsx("div", {
3917
+ className: "text-xs text-text-secondary",
3918
+ children: agent.toolAllowlist.length > 0 ? agent.toolAllowlist.map(describeRule).join(" · ") : tr("fluidgrids.agents.noTools", "No tools allowed")
3919
+ }),
3920
+ /* @__PURE__ */ jsx("div", {
3921
+ className: "text-xs text-text-tertiary",
3922
+ children: tr("fluidgrids.agents.connectionCount", "{{count}} connections in scope", { count: agent.connectionScope.length })
3923
+ }),
3924
+ /* @__PURE__ */ jsx(AgentGrants, {
3925
+ agent,
3926
+ loadGrants: grants,
3927
+ onGrant: grant,
3928
+ onRevoke: revoke
3926
3929
  })
3927
- }, destination.id))
3928
- ]
3930
+ ]
3931
+ }, agent.id))
3929
3932
  })
3930
3933
  ]
3931
3934
  });
@@ -5202,6 +5205,7 @@ function isRecord(value) {
5202
5205
  }
5203
5206
 
5204
5207
  // src/modules/settings/pages/SettingsPage.tsx
5208
+ var SettingsPage_exports = /* @__PURE__ */ __exportAll({ SettingsPage: () => SettingsPage });
5205
5209
  /**
5206
5210
  * A workflow's `definitionJson.config` (a sibling of `data`, not nested inside it): the same
5207
5211
  * shared type the SDK exports, so the form and typed readers agree on every field.
@@ -5984,38 +5988,4 @@ function SettingsPage({ workflowKey: workflowKeyProp } = {}) {
5984
5988
  });
5985
5989
  }
5986
5990
 
5987
- // src/modules/settings/api/ManagedAiPolicyAuthority.tsx
5988
- /** Hosts explicitly bridge their authority; a missing profile never becomes an embed fallback. */
5989
- function ManagedAiPolicyAuthorityProvider({ scope, children }) {
5990
- const { authority, url, actorId, workspaceId, organizationId, tenantId, projectId, authToken, workspaceToken } = scope ?? {};
5991
- const value = useMemo(() => {
5992
- if (!authority || !url || !actorId || !workspaceId || !organizationId || !tenantId || !authToken) return null;
5993
- return Object.freeze({
5994
- authority,
5995
- url,
5996
- actorId,
5997
- workspaceId,
5998
- organizationId,
5999
- tenantId,
6000
- projectId,
6001
- authToken,
6002
- workspaceToken
6003
- });
6004
- }, [
6005
- authority,
6006
- url,
6007
- actorId,
6008
- workspaceId,
6009
- organizationId,
6010
- tenantId,
6011
- projectId,
6012
- authToken,
6013
- workspaceToken
6014
- ]);
6015
- return /* @__PURE__ */ jsx(ManagedAiAuthorityContext.Provider, {
6016
- value,
6017
- children
6018
- });
6019
- }
6020
-
6021
- export { SettingsPage as n, ManagedAiPolicyAuthorityProvider as t };
5991
+ export { SettingsPage_exports as n, SettingsPage as t };