@burdenoff/fluidgrids-fe-sdk 2026.1112.7 → 2026.1112.9

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 (86) hide show
  1. package/dist/{AiStudioPage-QGTFaVpx.js → AiStudioPage-DfoV1Olc.js} +5 -5
  2. package/dist/ApprovalInboxPage-Bb8H9ruw.js +1317 -0
  3. package/dist/{DataSinkDashboardPage-kyzcjpzL.js → DataSinkDashboardPage-Dt3xTBb8.js} +1 -1
  4. package/dist/{DataTablesPage-0U89KNBt.js → DataTablesPage-Zz23QZQp.js} +6 -4
  5. package/dist/Disclosure-DQiUKrqd.js +41 -0
  6. package/dist/{EntityTagPicker-dxfzv8aO.js → EntityTagPicker-CuZAMby_.js} +1 -1
  7. package/dist/{ExecutionDetailPage-D4Mdbzb2.js → ExecutionDetailPage-CcUxFexZ.js} +15 -14
  8. package/dist/{ExecutionsListPage-g_SPxAGX.js → ExecutionsListPage-JpByw6nA.js} +11 -9
  9. package/dist/{FailureTriagePanel-D4lJ4zqQ.js → FailureTriagePanel-BjatYyAa.js} +1 -1
  10. package/dist/{FluidGridsProvider-B1hgCbxG.js → FluidGridsProvider-TQ-aDPtn.js} +3 -3
  11. package/dist/{HistoryPage-C7NwkVNC.js → HistoryPage-s9pl5gyl.js} +2 -2
  12. package/dist/{InsightsPage-D9B_qW8e.js → InsightsPage-CtjJCky-.js} +4 -4
  13. package/dist/{MissingNodesInstallModal-BkkgeFeN.js → MissingNodesInstallModal-olEc52p2.js} +1 -1
  14. package/dist/{NodesPage-BEeUbIts.js → NodesPage-BSNpOUMX.js} +2 -2
  15. package/dist/OpenTemplateAsCodeButton-Dh3ee2OR.js +94 -0
  16. package/dist/PaidTemplateConfirm-DjmVi_m-.js +28 -0
  17. package/dist/{RunActionsMenu-DmO14hEE.js → RunActionsMenu-BE_1r3bF.js} +1 -50
  18. package/dist/{RunnersPage-CvAPHdfU.js → RunnersPage-CAt4YDhB.js} +2 -1
  19. package/dist/{SettingsPage-k21044-Z.js → SettingsPage-ywyjIAL8.js} +6 -6
  20. package/dist/{TagFilterSelect-CqhxcDjh.js → TagFilterSelect-DcusTt1p.js} +1 -1
  21. package/dist/{TaskQueuesPage-B86p51gH.js → TaskQueuesPage-C2dkenVF.js} +2 -2
  22. package/dist/TechnicalDetails-D4xGy201.js +19 -0
  23. package/dist/{TemplateCard-DanOe65C.js → TemplateCard--WYJZlA_.js} +293 -36
  24. package/dist/{TemplateConfigurationModal-CeMpPEhT.js → TemplateConfigurationModal-cd0-QVMZ.js} +20 -11
  25. package/dist/{TemplateDetailPanel-DnoA6mC3.js → TemplateDetailPanel-C-2JuXg6.js} +1 -1
  26. package/dist/TemplatePreview-BCnGjMuN.js +646 -0
  27. package/dist/{TemplatesPage-C7f2xq12.js → TemplatesPage-u7bXJYfv.js} +377 -138
  28. package/dist/{VariablesPage-BtoLjmUA.js → VariablesPage-CPL34WBc.js} +2 -358
  29. package/dist/{VersionDiffDialog-pLpG_I1r.js → VersionDiffDialog-c27yet_-.js} +1 -1
  30. package/dist/{WorkflowBuilder-Aslfa2vj.js → WorkflowBuilder-BWxBmBoP.js} +3 -3
  31. package/dist/{WorkflowBuilder-DY1_KJiV.js → WorkflowBuilder-DxAAPHsn.js} +8 -8
  32. package/dist/{WorkflowProposalCard-BjYAZH3q.js → WorkflowProposalCard-BvmJ5ea8.js} +1 -1
  33. package/dist/{WorkflowSearchRedirect-iPQBL6WO.js → WorkflowSearchRedirect-BGKduhfZ.js} +1 -1
  34. package/dist/{WorkflowShareDialog-DB-qJLB2.js → WorkflowShareDialog-BRwTF9QB.js} +1 -1
  35. package/dist/{WorkflowsList-rOxlI49U.js → WorkflowsList-Bocag4TX.js} +9 -8
  36. package/dist/{agentTools-B8gnHFVM.js → agentTools-JHtYGgd7.js} +3 -3
  37. package/dist/{api-JpNuowX4.js → api-Dw8nfVRD.js} +1 -1
  38. package/dist/{api-Dvitj1aT.js → api-m8hX_l2k.js} +1 -1
  39. package/dist/{canvasNoteTools-Cj7MvVDH.js → canvasNoteTools-B2ouyIbj.js} +2 -2
  40. package/dist/{HumanRequestCard-lbWDpWd7.js → channel-CZ70oI81.js} +5 -228
  41. package/dist/{code-editor-field-yJZEDJBi.js → code-editor-field-CWGZyNGM.js} +1 -1
  42. package/dist/executions.js +2 -2
  43. package/dist/{feature-flags-DM5myWaD.js → feature-flags-B_b8PJ6Z.js} +63 -3
  44. package/dist/flags.d.ts +1 -1
  45. package/dist/flags.js +1 -1
  46. package/dist/{flow-builder-dvqpKs8d.js → flow-builder-DzKPrW2r.js} +535 -313
  47. package/dist/{graphql-operation-types-z2oXfQvM.js → graphql-operation-types-DZVhCBEi.js} +250 -1
  48. package/dist/history.js +1 -1
  49. package/dist/{index-BcdtKBM-.d.ts → index-B4g88tCa.d.ts} +15 -1
  50. package/dist/{index-DNo9aFUT.d.ts → index-CGCAcRKo.d.ts} +32 -2
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.js +38 -38
  53. package/dist/{node-detail-view-ZnJ3Hvvo.js → node-detail-view-DNilUGcx.js} +9 -9
  54. package/dist/{node-editor-tab-body-BuiIr7VD.js → node-editor-tab-body-XBedxkpN.js} +3 -3
  55. package/dist/{node-endpoint-configs-typvqw2c.js → node-endpoint-configs-BI7zc8eM.js} +1 -1
  56. package/dist/nodes.js +1 -1
  57. package/dist/observability.d.ts +1 -1
  58. package/dist/observability.js +2 -2
  59. package/dist/{popover-mexhIFP3.js → popover-BC4r84K0.js} +3 -3
  60. package/dist/{quick-config-panel-EV66lJOM.js → quick-config-panel-BPmrhIOb.js} +3 -3
  61. package/dist/{schedule-panel-BeLfDNFh.js → schedule-panel-DJN1TDQH.js} +8 -8
  62. package/dist/settings.js +1 -1
  63. package/dist/{sidebar-node-editor-D85Moj5d.js → sidebar-node-editor-DKdfcd2r.js} +3 -3
  64. package/dist/templates.js +2 -2
  65. package/dist/{useAgentDefinitions-Uy6nhpKN.js → useAgentDefinitions-B4sw7bLw.js} +1 -1
  66. package/dist/{useDurableParity-04-5PTk2.js → useDurableParity-B3h8jqP0.js} +2 -2
  67. package/dist/{useEntityTags-D5C0tdLs.js → useEntityTags-BpDeLhzV.js} +1 -1
  68. package/dist/{useExecutions-DJXtbA15.js → useExecutions-BbkBSYpS.js} +2 -2
  69. package/dist/{useObservability-Dfgh4yjv.js → useObservability-BASo2sA3.js} +1 -1
  70. package/dist/{useRunHistory-BZQ3t68Q.js → useRunHistory-C3Pv_SJW.js} +2 -2
  71. package/dist/useVariables-cI8uLwye.js +361 -0
  72. package/dist/useWorkflowCodeExport-k5Emm4ZS.js +226 -0
  73. package/dist/{useWorkflows-CP-d3ygY.js → useWorkflows-CRUlQBd3.js} +2 -2
  74. package/dist/workflow-builder.js +4 -4
  75. package/dist/workflow-code-transfer-Cshsgf2H.js +375 -0
  76. package/dist/workflow-host.d.ts +1 -1
  77. package/dist/workflow-host.js +1 -1
  78. package/dist/workflow-pages.d.ts +1 -1
  79. package/dist/workflow-pages.js +19 -19
  80. package/dist/workflow-transfer-CQnQC-ke.js +104 -0
  81. package/dist/workflows-list.js +1 -1
  82. package/package.json +1 -1
  83. package/dist/ApprovalInboxPage-QAJim81n.js +0 -559
  84. package/dist/QuotaExhaustedDialog-Bj7AG_0y.js +0 -55
  85. package/dist/TemplatePreview-DY4GxnfC.js +0 -231
  86. package/dist/useWorkflowCodeExport-D0zapXXm.js +0 -643
@@ -0,0 +1,1317 @@
1
+ "use client";
2
+ import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
+ import { n as useNavigationV2 } from "./useNavigationV2-C2WeqN7d.js";
5
+ import { _ as throwIfResultError, h as isForbiddenError, m as describeOperationError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
6
+ import { a as useRecipientLabel, i as notAnswerableText, n as ApprovalApprovers, r as answeredToastText, t as channelLabel } from "./channel-CZ70oI81.js";
7
+ import { n as useRespondToHumanRequest, t as usePendingHumanRequests } from "./useHumanRequests-ZiR0sSzS.js";
8
+ import { t as TechnicalDetails } from "./TechnicalDetails-D4xGy201.js";
9
+ import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
10
+ import { r as nodeDisplayTitle, t as buildNodeDisplayMap } from "./run-display-BycKYSU0.js";
11
+ import { i as SdkPermissionButton, s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-dcxg6zG6.js";
12
+ import { useCallback, useEffect, useState } from "react";
13
+ import { Link, useNavigate } from "react-router";
14
+ import { Icon } from "@iconify/react";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
+ import { toast } from "sonner";
17
+ import { gql } from "@apollo/client";
18
+ import { Badge, Card, CardContent, CardHeader, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Textarea } from "@burdenoff/fe-libs/ui";
19
+ import { Button as Button$1 } from "@burdenoff/fe-libs/button";
20
+
21
+ // src/modules/human-response/utils/approval-labels.ts
22
+ /**
23
+ * The raw ids every Approvals card keeps one click away (Navigation v2,
24
+ * BOFF-11848): the workflow key, the run id and the step (node) id.
25
+ */
26
+ function approvalTechnicalRows(ids, tr) {
27
+ return [
28
+ {
29
+ label: tr("fluidgrids.approvals.technicalWorkflowKey", "Workflow key"),
30
+ value: ids.workflowKey
31
+ },
32
+ {
33
+ label: tr("fluidgrids.approvals.technicalRunId", "Run ID"),
34
+ value: ids.runId
35
+ },
36
+ {
37
+ label: tr("fluidgrids.approvals.technicalStepId", "Step ID"),
38
+ value: ids.nodeId
39
+ }
40
+ ];
41
+ }
42
+ /**
43
+ * Where an Approvals card comes from, in words (Navigation v2): "Step: Sort
44
+ * the ticket in Support desk", or "In Support desk" when the step's name is
45
+ * not known.
46
+ */
47
+ function approvalSourceText(workflow, step, tr) {
48
+ return step ? tr("fluidgrids.approvals.stepInWorkflow", "Step: {{step}} in {{workflow}}", {
49
+ step,
50
+ workflow
51
+ }) : tr("fluidgrids.approvals.inWorkflow", "In {{workflow}}", { workflow });
52
+ }
53
+
54
+ // src/modules/human-response/components/ApprovalSource.tsx
55
+ /** "Open run": the run this card belongs to, on the Runs page. */
56
+ function OpenRunLink({ runId }) {
57
+ const tr = useTr();
58
+ return /* @__PURE__ */ jsx(Link, {
59
+ to: `../runs/${encodeURIComponent(runId)}`,
60
+ "data-testid": "approval-open-run",
61
+ className: "font-medium text-text-link underline-offset-2 hover:text-text-linkHover hover:underline",
62
+ children: tr("fluidgrids.approvals.openRun", "Open run")
63
+ });
64
+ }
65
+ /** "Step: {step} in {workflow} · Open run" under an ask's title. */
66
+ function ApprovalSource({ workflow, step, runId }) {
67
+ const tr = useTr();
68
+ return /* @__PURE__ */ jsxs("p", {
69
+ className: "flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-xs text-text-secondary",
70
+ "data-testid": "approval-source",
71
+ children: [/* @__PURE__ */ jsx("span", {
72
+ className: "min-w-0 break-words",
73
+ children: approvalSourceText(workflow, step, tr)
74
+ }), /* @__PURE__ */ jsx(OpenRunLink, { runId })]
75
+ });
76
+ }
77
+ /** The raw ids of a card, one click away under "Show technical details". */
78
+ function ApprovalTechnicalDetails({ rows, children }) {
79
+ return /* @__PURE__ */ jsxs(TechnicalDetails, {
80
+ "data-testid": "approval-technical-details",
81
+ children: [/* @__PURE__ */ jsx("dl", {
82
+ className: "space-y-1 text-xs",
83
+ children: rows.map((row) => /* @__PURE__ */ jsxs("div", {
84
+ className: "flex flex-wrap gap-x-2",
85
+ children: [/* @__PURE__ */ jsx("dt", {
86
+ className: "font-medium text-text-secondary",
87
+ children: row.label
88
+ }), /* @__PURE__ */ jsx("dd", {
89
+ className: "break-all font-mono text-text-primary",
90
+ children: row.value
91
+ })]
92
+ }, row.label))
93
+ }), children]
94
+ });
95
+ }
96
+
97
+ // src/modules/human-response/components/HumanRequestCard.tsx
98
+ /**
99
+ * One "Send and Wait for Response" ask in the in-app inbox (BOFF-7651): what was asked, of whom,
100
+ * through which channel and until when — and, when the caller may answer it in-app, the controls
101
+ * to do so (Approve / Decline, a text box, or the custom form). An approval with several approvers
102
+ * (BOFF-9956) also shows where its quorum stands and takes an optional comment with the answer.
103
+ *
104
+ * Navigation v2 (BOFF-11848, FluidGrids only): the card names its workflow (and step) with an
105
+ * "Open run" link instead of the raw workflow-key badge, which moves under "Show technical
106
+ * details" with the run and step ids; a required field says "(required)" instead of " *".
107
+ */
108
+ const CHANNEL_ICON = {
109
+ EMAIL: "mdi:email-outline",
110
+ SLACK: "mdi:slack",
111
+ IN_APP: "mdi:bell-outline"
112
+ };
113
+ function inputType(field) {
114
+ const type = (field.type || "text").toLowerCase();
115
+ return [
116
+ "email",
117
+ "number",
118
+ "date",
119
+ "tel",
120
+ "url"
121
+ ].includes(type) ? type : "text";
122
+ }
123
+ function FormControls({ requestId, fields, values, onChange }) {
124
+ const tr = useTr();
125
+ const v2 = useNavigationV2();
126
+ const marker = v2 ? ` ${tr("fluidgrids.humanResponse.requiredMarker", "(required)")}` : " *";
127
+ return /* @__PURE__ */ jsx("div", {
128
+ className: "space-y-3",
129
+ children: fields.map((field) => {
130
+ const id = `human-field-${requestId}-${field.name}`;
131
+ const required = field.required || void 0;
132
+ const type = (field.type || "text").toLowerCase();
133
+ return /* @__PURE__ */ jsxs("div", {
134
+ className: "space-y-1.5",
135
+ children: [/* @__PURE__ */ jsxs(Label, {
136
+ htmlFor: id,
137
+ children: [field.label || field.name, field.required && /* @__PURE__ */ jsx("span", {
138
+ "aria-hidden": "true",
139
+ className: v2 ? "text-text-secondary" : void 0,
140
+ children: marker
141
+ })]
142
+ }), type === "textarea" ? /* @__PURE__ */ jsx(Textarea, {
143
+ id,
144
+ "aria-required": required,
145
+ value: values[field.name] ?? "",
146
+ placeholder: field.placeholder ?? void 0,
147
+ onChange: (event) => onChange(field.name, event.target.value)
148
+ }) : type === "dropdown" || type === "select" ? /* @__PURE__ */ jsxs("select", {
149
+ id,
150
+ "aria-required": required,
151
+ className: "w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 text-sm text-text-primary",
152
+ value: values[field.name] ?? "",
153
+ onChange: (event) => onChange(field.name, event.target.value),
154
+ children: [/* @__PURE__ */ jsx("option", {
155
+ value: "",
156
+ children: "—"
157
+ }), (field.options ?? []).map((option) => /* @__PURE__ */ jsx("option", {
158
+ value: option,
159
+ children: option
160
+ }, option))]
161
+ }) : /* @__PURE__ */ jsx(Input, {
162
+ id,
163
+ "aria-required": required,
164
+ type: inputType(field),
165
+ value: values[field.name] ?? "",
166
+ placeholder: field.placeholder ?? void 0,
167
+ onChange: (event) => onChange(field.name, event.target.value)
168
+ })]
169
+ }, field.name);
170
+ })
171
+ });
172
+ }
173
+ /** Approve / Decline, with an optional comment that is recorded with the answer (BOFF-9956). */
174
+ function ApprovalControls({ request, loading, onAnswer }) {
175
+ const tr = useTr();
176
+ const [comment, setComment] = useState("");
177
+ const note = comment.trim() ? { comment: comment.trim() } : {};
178
+ const commentId = `human-comment-${request.id}`;
179
+ return /* @__PURE__ */ jsxs("div", {
180
+ className: "space-y-2 pt-1",
181
+ children: [/* @__PURE__ */ jsxs("div", {
182
+ className: "space-y-1.5",
183
+ children: [/* @__PURE__ */ jsx(Label, {
184
+ htmlFor: commentId,
185
+ children: tr("fluidgrids.humanResponse.commentLabel", "Comment (optional)")
186
+ }), /* @__PURE__ */ jsx(Textarea, {
187
+ id: commentId,
188
+ value: comment,
189
+ maxLength: 2e3,
190
+ placeholder: tr("fluidgrids.humanResponse.commentPlaceholder", "Why you approve or decline. It is saved with your answer on the run."),
191
+ onChange: (event) => setComment(event.target.value),
192
+ "data-testid": "human-request-comment"
193
+ })]
194
+ }), /* @__PURE__ */ jsxs("div", {
195
+ className: "flex flex-wrap items-center gap-2",
196
+ children: [/* @__PURE__ */ jsxs(Button$1, {
197
+ "data-agent": "confirm",
198
+ size: "sm",
199
+ disabled: loading,
200
+ onClick: () => onAnswer({
201
+ action: "approve",
202
+ ...note
203
+ }),
204
+ "data-testid": "human-request-approve",
205
+ children: [/* @__PURE__ */ jsx(Icon, {
206
+ icon: "mdi:check",
207
+ className: "mr-1.5",
208
+ width: 16
209
+ }), request.approveLabel]
210
+ }), request.approvalType === "double" && /* @__PURE__ */ jsxs(Button$1, {
211
+ "data-agent": "confirm",
212
+ size: "sm",
213
+ variant: "destructive",
214
+ disabled: loading,
215
+ onClick: () => onAnswer({
216
+ action: "decline",
217
+ ...note
218
+ }),
219
+ "data-testid": "human-request-decline",
220
+ children: [/* @__PURE__ */ jsx(Icon, {
221
+ icon: "mdi:close",
222
+ className: "mr-1.5",
223
+ width: 16
224
+ }), request.declineLabel]
225
+ })]
226
+ })]
227
+ });
228
+ }
229
+ /** How the caller answers in-app: Approve/Decline, a text box, or the custom form. */
230
+ function AnswerArea({ request, loading, onAnswer }) {
231
+ const tr = useTr();
232
+ const [text, setText] = useState("");
233
+ const [values, setValues] = useState({});
234
+ if (request.responseType === "APPROVAL") return /* @__PURE__ */ jsx(ApprovalControls, {
235
+ request,
236
+ loading,
237
+ onAnswer
238
+ });
239
+ const missingRequired = request.responseType === "CUSTOM_FORM" && request.fields.some((field) => field.required && !(values[field.name] ?? "").trim());
240
+ const emptyText = request.responseType === "FREE_TEXT" && !text.trim();
241
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
242
+ request.responseType === "FREE_TEXT" && /* @__PURE__ */ jsxs("div", {
243
+ className: "space-y-1.5",
244
+ children: [/* @__PURE__ */ jsx(Label, {
245
+ htmlFor: `human-text-${request.id}`,
246
+ children: tr("fluidgrids.humanResponse.yourResponse", "Your response")
247
+ }), /* @__PURE__ */ jsx(Textarea, {
248
+ id: `human-text-${request.id}`,
249
+ value: text,
250
+ onChange: (event) => setText(event.target.value)
251
+ })]
252
+ }),
253
+ request.responseType === "CUSTOM_FORM" && /* @__PURE__ */ jsx(FormControls, {
254
+ requestId: request.id,
255
+ fields: request.fields,
256
+ values,
257
+ onChange: (name, value) => setValues((current) => ({
258
+ ...current,
259
+ [name]: value
260
+ }))
261
+ }),
262
+ /* @__PURE__ */ jsx("div", {
263
+ className: "flex flex-wrap items-center gap-2 pt-1",
264
+ children: /* @__PURE__ */ jsxs(Button$1, {
265
+ "data-agent": "confirm",
266
+ size: "sm",
267
+ disabled: loading || missingRequired || emptyText,
268
+ onClick: () => onAnswer(request.responseType === "FREE_TEXT" ? { text } : { values }),
269
+ "data-testid": "human-request-submit",
270
+ children: [/* @__PURE__ */ jsx(Icon, {
271
+ icon: "mdi:send",
272
+ className: "mr-1.5",
273
+ width: 16
274
+ }), request.submitLabel]
275
+ })
276
+ })
277
+ ] });
278
+ }
279
+ function HumanRequestCard({ request, onAnswered, workflowName, stepName }) {
280
+ const tr = useTr();
281
+ const v2 = useNavigationV2();
282
+ const { respond, loading } = useRespondToHumanRequest();
283
+ const submit = async (answer) => {
284
+ try {
285
+ const decision = await respond(request.id, answer);
286
+ toast.success(answeredToastText(decision, tr));
287
+ onAnswered();
288
+ } catch (err) {
289
+ const failed = tr("fluidgrids.humanResponse.failed", "Your response could not be recorded");
290
+ toast.error(failed, { description: describeOperationError(err, failed) });
291
+ }
292
+ };
293
+ const recipient = useRecipientLabel(request, request.channel === "IN_APP" ? tr("fluidgrids.humanResponse.anyMember", "any workspace member") : "");
294
+ return /* @__PURE__ */ jsxs(Card, {
295
+ className: "border border-border-secondary bg-background-secondary",
296
+ "data-testid": "human-request-card",
297
+ "data-request-id": request.id,
298
+ children: [/* @__PURE__ */ jsxs(CardHeader, {
299
+ className: "pb-3",
300
+ children: [
301
+ /* @__PURE__ */ jsxs("div", {
302
+ className: "flex flex-wrap items-center justify-between gap-2",
303
+ children: [/* @__PURE__ */ jsxs("div", {
304
+ className: "flex min-w-0 items-center gap-2",
305
+ children: [/* @__PURE__ */ jsx(Icon, {
306
+ icon: CHANNEL_ICON[request.channel] ?? "mdi:account-question",
307
+ width: 18,
308
+ className: "shrink-0 text-text-secondary"
309
+ }), /* @__PURE__ */ jsx("span", {
310
+ className: "truncate text-sm font-semibold text-text-primary",
311
+ children: request.title
312
+ })]
313
+ }), !v2 && /* @__PURE__ */ jsx(Badge, {
314
+ variant: "outline",
315
+ className: "font-mono text-[10px]",
316
+ children: request.workflowKey
317
+ })]
318
+ }),
319
+ v2 && /* @__PURE__ */ jsx(ApprovalSource, {
320
+ workflow: workflowName ?? request.workflowKey,
321
+ step: stepName,
322
+ runId: request.runId
323
+ }),
324
+ /* @__PURE__ */ jsxs("p", {
325
+ className: "text-xs text-text-secondary",
326
+ children: [tr("fluidgrids.humanResponse.askedVia", "Asked {{recipient}} via {{channel}}", {
327
+ recipient,
328
+ channel: channelLabel(request.channel, tr)
329
+ }), request.expiresAt && ` · ${tr("fluidgrids.humanResponse.until", "until {{time}}", { time: new Date(request.expiresAt).toLocaleString() })}`]
330
+ })
331
+ ]
332
+ }), /* @__PURE__ */ jsxs(CardContent, {
333
+ className: "space-y-3",
334
+ children: [
335
+ request.message && /* @__PURE__ */ jsx("p", {
336
+ className: "whitespace-pre-wrap break-words text-sm text-text-primary",
337
+ children: request.message
338
+ }),
339
+ /* @__PURE__ */ jsx(ApprovalApprovers, { request }),
340
+ !request.canRespond && /* @__PURE__ */ jsx("p", {
341
+ className: "text-xs text-text-secondary",
342
+ children: notAnswerableText(request, channelLabel(request.channel, tr), tr)
343
+ }),
344
+ request.canRespond && /* @__PURE__ */ jsx(AnswerArea, {
345
+ request,
346
+ loading,
347
+ onAnswer: submit
348
+ }),
349
+ v2 && /* @__PURE__ */ jsx(ApprovalTechnicalDetails, { rows: approvalTechnicalRows(request, tr) })
350
+ ]
351
+ })]
352
+ });
353
+ }
354
+
355
+ // src/modules/tool-approval/hooks/useToolApprovals.ts
356
+ /**
357
+ * Custom hooks for the AI Agent tool-approval inbox (BOFF-7521).
358
+ * Queries/mutates wspace-workflow-svc via the federated GraphQL gateway —
359
+ * mirrors the variables module's hook pattern
360
+ * (`modules/variables/hooks/useVariables.ts`).
361
+ */
362
+ const TOOL_APPROVAL_FIELDS = gql`
363
+ fragment ToolApprovalRequestFields on ToolApprovalRequest {
364
+ id
365
+ runId
366
+ nodeId
367
+ workflowId
368
+ workflowKey
369
+ status
370
+ toolCalls {
371
+ callId
372
+ name
373
+ argsJson
374
+ }
375
+ decisions {
376
+ callId
377
+ decision
378
+ reason
379
+ }
380
+ timeoutAt
381
+ timeoutAction
382
+ requestedAt
383
+ decidedAt
384
+ decidedBy
385
+ }
386
+ `;
387
+ const GET_PENDING_TOOL_APPROVALS = gql`
388
+ ${TOOL_APPROVAL_FIELDS}
389
+ query GetPendingToolApprovals($limit: Int, $offset: Int) {
390
+ pendingToolApprovals(limit: $limit, offset: $offset) {
391
+ totalCount
392
+ items {
393
+ ...ToolApprovalRequestFields
394
+ }
395
+ }
396
+ }
397
+ `;
398
+ const DECIDE_TOOL_APPROVAL = gql`
399
+ ${TOOL_APPROVAL_FIELDS}
400
+ mutation DecideToolApproval(
401
+ $id: ID!
402
+ $decisions: [ToolApprovalDecisionInput!]!
403
+ ) {
404
+ decideToolApproval(id: $id, decisions: $decisions) {
405
+ success
406
+ error
407
+ resumedFromNodeId
408
+ request {
409
+ ...ToolApprovalRequestFields
410
+ }
411
+ }
412
+ }
413
+ `;
414
+ /**
415
+ * Polls the pending tool-approval inbox. Short polling interval (not a
416
+ * subscription) — approvals are rare/low-volume and the caller wants a
417
+ * near-real-time inbox without standing up a dedicated subscription channel
418
+ * for a v1 feature.
419
+ *
420
+ * Pass `enabled: false` when the caller lacks `tool-approval-request:read`:
421
+ * the query is then never sent, so the gateway's FORBIDDEN never reaches the
422
+ * shell's global error toast (BOFF-10104). If the gateway still denies the
423
+ * query, `forbidden` turns true and polling stops.
424
+ */
425
+ function usePendingToolApprovals(options) {
426
+ const limit = options?.limit ?? 50;
427
+ const offset = options?.offset ?? 0;
428
+ const pollIntervalMs = options?.pollIntervalMs ?? 15e3;
429
+ const enabled = options?.enabled ?? true;
430
+ const [items, setItems] = useState([]);
431
+ const [totalCount, setTotalCount] = useState(0);
432
+ const [loading, setLoading] = useState(enabled);
433
+ const [error, setError] = useState(null);
434
+ const [forbidden, setForbidden] = useState(false);
435
+ const fetchData = useCallback(async () => {
436
+ if (!enabled) return;
437
+ setError(null);
438
+ try {
439
+ const client = getFluidGridsClient();
440
+ const result = throwIfResultError(await client.query({
441
+ query: GET_PENDING_TOOL_APPROVALS,
442
+ variables: {
443
+ limit,
444
+ offset
445
+ },
446
+ fetchPolicy: "network-only"
447
+ }));
448
+ setItems(result.data?.pendingToolApprovals?.items ?? []);
449
+ setTotalCount(result.data?.pendingToolApprovals?.totalCount ?? 0);
450
+ } catch (err) {
451
+ if (isForbiddenError(err)) {
452
+ setForbidden(true);
453
+ setItems([]);
454
+ setTotalCount(0);
455
+ } else setError(err);
456
+ } finally {
457
+ setLoading(false);
458
+ }
459
+ }, [
460
+ enabled,
461
+ limit,
462
+ offset
463
+ ]);
464
+ useEffect(() => {
465
+ setForbidden(false);
466
+ if (!enabled) {
467
+ setItems([]);
468
+ setTotalCount(0);
469
+ setError(null);
470
+ }
471
+ }, [enabled]);
472
+ useEffect(() => {
473
+ if (!enabled || forbidden) {
474
+ setLoading(false);
475
+ return;
476
+ }
477
+ setLoading(true);
478
+ fetchData();
479
+ if (pollIntervalMs <= 0) return;
480
+ const interval = setInterval(fetchData, pollIntervalMs);
481
+ return () => clearInterval(interval);
482
+ }, [
483
+ enabled,
484
+ fetchData,
485
+ forbidden,
486
+ pollIntervalMs
487
+ ]);
488
+ return {
489
+ items,
490
+ totalCount,
491
+ loading,
492
+ error,
493
+ forbidden: forbidden || !enabled,
494
+ refetch: fetchData
495
+ };
496
+ }
497
+ function useDecideToolApproval() {
498
+ const [loading, setLoading] = useState(false);
499
+ const [error, setError] = useState(null);
500
+ const decide = useCallback(async (id, decisions) => {
501
+ setLoading(true);
502
+ setError(null);
503
+ try {
504
+ const client = getFluidGridsClient();
505
+ const response = throwIfResultError(await client.mutate({
506
+ mutation: DECIDE_TOOL_APPROVAL,
507
+ variables: {
508
+ id,
509
+ decisions
510
+ }
511
+ })).data?.decideToolApproval;
512
+ if (!response?.success) throw new Error(response?.error ?? "Failed to record the decision");
513
+ return response;
514
+ } catch (err) {
515
+ setError(err);
516
+ throw err;
517
+ } finally {
518
+ setLoading(false);
519
+ }
520
+ }, []);
521
+ return {
522
+ decide,
523
+ decideAll: useCallback(async (request, decision, reason) => {
524
+ return decide(request.id, request.toolCalls.map((call) => ({
525
+ callId: call.callId,
526
+ decision,
527
+ reason
528
+ })));
529
+ }, [decide]),
530
+ loading,
531
+ error
532
+ };
533
+ }
534
+
535
+ // src/modules/tool-approval/hooks/useApprovalWorkflows.ts
536
+ /**
537
+ * Names for the Approvals cards (Navigation v2, BOFF-11848): which workflow
538
+ * each ask or AI action belongs to, and the name of the step that asked.
539
+ *
540
+ * Neither `ToolApprovalRequest` nor `WorkflowHumanRequest` carries a name,
541
+ * only the workflow key (and, for AI actions, the definition id), so the page
542
+ * looks each workflow up once. A failed lookup (a deleted workflow, no access)
543
+ * leaves the card on the key; nothing is shown as an error.
544
+ */
545
+ const GET_APPROVAL_WORKFLOW = gql`
546
+ query GetApprovalWorkflow($id: ID, $workflowKey: String) {
547
+ workflowDefinition(id: $id, workflowKey: $workflowKey) {
548
+ id
549
+ workflowKey
550
+ name
551
+ definitionJson
552
+ }
553
+ }
554
+ `;
555
+ /** At most this many workflows are looked up for one page of cards. */
556
+ const MAX_WORKFLOW_LOOKUPS = 20;
557
+ /** The lookup key of a ref: the definition id when known, else the key. */
558
+ function approvalWorkflowRefKey(ref) {
559
+ return ref.workflowId ? `id:${ref.workflowId}` : `key:${ref.workflowKey}`;
560
+ }
561
+ /**
562
+ * The unique lookup keys of the cards, in first-seen order, capped at
563
+ * MAX_WORKFLOW_LOOKUPS, joined into one string: a poll that returns the same
564
+ * cards gives the same string, so nothing is looked up again.
565
+ */
566
+ function workflowLookupSignature(refs) {
567
+ const keys = /* @__PURE__ */ new Set();
568
+ for (const ref of refs) {
569
+ keys.add(approvalWorkflowRefKey(ref));
570
+ if (keys.size >= 20) break;
571
+ }
572
+ return [...keys].join("\n");
573
+ }
574
+ /** The query variables for one lookup key ("id:…" or "key:…"). */
575
+ function lookupVariables(key) {
576
+ return key.startsWith("id:") ? { id: key.slice(3) } : { workflowKey: key.slice(4) };
577
+ }
578
+ async function lookUp(key) {
579
+ try {
580
+ const definition = throwIfResultError(await getFluidGridsClient().query({
581
+ query: GET_APPROVAL_WORKFLOW,
582
+ variables: lookupVariables(key),
583
+ fetchPolicy: "cache-first"
584
+ })).data?.workflowDefinition;
585
+ if (!definition?.name) return null;
586
+ return {
587
+ name: definition.name,
588
+ steps: buildNodeDisplayMap(definition.definitionJson)
589
+ };
590
+ } catch {
591
+ return null;
592
+ }
593
+ }
594
+ /**
595
+ * Looks up the workflows behind the cards while `enabled` (Navigation v2,
596
+ * and the caller may read workflows). Returns what is known so far, keyed by
597
+ * `approvalWorkflowRefKey`.
598
+ */
599
+ function useApprovalWorkflows(refs, enabled) {
600
+ const signature = workflowLookupSignature(refs);
601
+ const [known, setKnown] = useState(() => /* @__PURE__ */ new Map());
602
+ useEffect(() => {
603
+ if (!enabled || !signature) return;
604
+ let active = true;
605
+ Promise.all(signature.split("\n").map(async (key) => [key, await lookUp(key)])).then((results) => {
606
+ if (!active) return;
607
+ setKnown((current) => {
608
+ const next = new Map(current);
609
+ for (const [key, info] of results) if (info) next.set(key, info);
610
+ return next;
611
+ });
612
+ });
613
+ return () => {
614
+ active = false;
615
+ };
616
+ }, [enabled, signature]);
617
+ return known;
618
+ }
619
+ /** "Step: Sort the ticket in Support desk" parts for a card, or the key. */
620
+ function approvalLabels(info, workflowKey, nodeId) {
621
+ const display = info?.steps[nodeId];
622
+ return {
623
+ workflow: info?.name ?? workflowKey,
624
+ step: display ? nodeDisplayTitle(display, nodeId) : null
625
+ };
626
+ }
627
+
628
+ // src/modules/tool-approval/utils/paging.ts
629
+ /**
630
+ * Paging for the Approvals inbox (BOFF-11848). Both lists (asks waiting for a
631
+ * person, and the AI assistant's actions waiting for an OK) load 50 at a time.
632
+ */
633
+ /** Items per page in each Approvals list. */
634
+ const APPROVALS_PAGE_SIZE = 50;
635
+ /**
636
+ * The offset to show for a requested one: answering the last item on a later
637
+ * page empties it, so step back to the last page that still has items.
638
+ * Derived while rendering, so it never needs an effect to correct itself.
639
+ */
640
+ function clampPageOffset(offset, total, pageSize = 50) {
641
+ if (offset <= 0 || offset < total) return Math.max(0, offset);
642
+ return Math.max(0, Math.ceil(total / pageSize) - 1) * pageSize;
643
+ }
644
+
645
+ // src/modules/tool-approval/components/InboxPager.tsx
646
+ /**
647
+ * "{from}–{to} of {total}" with Previous / Next, under each Approvals list
648
+ * (BOFF-7651 for asks; BOFF-11848 adds it to the AI assistant's actions, which
649
+ * had no pager, so anything past the first 50 could not be reached).
650
+ * Renders nothing while everything fits on one page.
651
+ */
652
+ function InboxPager({ offset, shown, total, onOffsetChange, "data-testid": testId }) {
653
+ const tr = useTr();
654
+ if (total <= 50) return null;
655
+ return /* @__PURE__ */ jsxs("div", {
656
+ className: "mt-4 flex items-center gap-3",
657
+ "data-testid": testId,
658
+ children: [
659
+ /* @__PURE__ */ jsx("span", {
660
+ className: "text-xs text-text-secondary",
661
+ children: tr("fluidgrids.humanResponse.pageRange", "{{from}}–{{to}} of {{total}}", {
662
+ from: offset + 1,
663
+ to: offset + shown,
664
+ total
665
+ })
666
+ }),
667
+ /* @__PURE__ */ jsx(Button$1, {
668
+ variant: "outline",
669
+ size: "sm",
670
+ disabled: offset === 0,
671
+ onClick: () => onOffsetChange(Math.max(0, offset - 50)),
672
+ children: tr("fluidgrids.humanResponse.previousPage", "Previous")
673
+ }),
674
+ /* @__PURE__ */ jsx(Button$1, {
675
+ variant: "outline",
676
+ size: "sm",
677
+ disabled: offset + 50 >= total,
678
+ onClick: () => onOffsetChange(offset + 50),
679
+ children: tr("fluidgrids.humanResponse.nextPage", "Next")
680
+ })
681
+ ]
682
+ });
683
+ }
684
+
685
+ // src/modules/tool-approval/utils/timeout.ts
686
+ /**
687
+ * Classifies timeout metadata without changing server-owned request status.
688
+ * The timeout sweep remains authoritative for auto-resolving WAITING runs.
689
+ */
690
+ function getToolApprovalTimeoutState(timeoutAt, nowMs = Date.now()) {
691
+ if (!timeoutAt) return "NONE";
692
+ const timeoutMs = Date.parse(timeoutAt);
693
+ if (!Number.isFinite(timeoutMs)) return "NONE";
694
+ return timeoutMs <= nowMs ? "DUE" : "ACTIVE";
695
+ }
696
+
697
+ // src/modules/tool-approval/utils/tool-summary.ts
698
+ /**
699
+ * Plain-language view of an AI assistant's proposed action on the Approvals
700
+ * inbox (Navigation v2, BOFF-11848): "The AI assistant wants to: Send email"
701
+ * with a short key/value summary of its arguments. The raw tool name and JSON
702
+ * stay available under "Show technical details".
703
+ */
704
+ /** How many arguments the summary lists before "and N more". */
705
+ const SUMMARY_MAX_ENTRIES = 6;
706
+ /** Longest value the summary shows before cutting it with "…". */
707
+ const SUMMARY_MAX_VALUE_LENGTH = 120;
708
+ /**
709
+ * Split an identifier into lower-case words: snake_case, kebab-case,
710
+ * dotted/namespaced (`github.search_issues`, `slack__post`) and camelCase
711
+ * (`searchIssues`, `HTTPRequest`).
712
+ */
713
+ function words(identifier) {
714
+ return identifier.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[\s_\-.:/]+/).map((word) => word.trim().toLowerCase()).filter(Boolean);
715
+ }
716
+ function sentenceCase(parts) {
717
+ const text = parts.join(" ");
718
+ return text.charAt(0).toUpperCase() + text.slice(1);
719
+ }
720
+ /**
721
+ * "send_email" → "Send email", "github.searchIssues" → "Github search issues".
722
+ * An empty or symbol-only name is returned as it is.
723
+ */
724
+ function friendlyToolName(name) {
725
+ const parts = words(name);
726
+ return parts.length > 0 ? sentenceCase(parts) : name;
727
+ }
728
+ /** "recipient_email" → "Recipient email". */
729
+ function friendlyArgumentName(key) {
730
+ return friendlyToolName(key);
731
+ }
732
+ function shorten(text) {
733
+ return text.length > 120 ? `${text.slice(0, 119)}…` : text;
734
+ }
735
+ function valueText(value) {
736
+ if (value === null || value === void 0) return "—";
737
+ if (typeof value === "string") return shorten(value);
738
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
739
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) return shorten(value.join(", "));
740
+ return shorten(JSON.stringify(value));
741
+ }
742
+ /**
743
+ * The top-level arguments of a tool call as label/value rows. Arguments that
744
+ * are not a JSON object (a list, a bare value, malformed JSON) give no rows:
745
+ * the raw JSON under the technical details says it all.
746
+ */
747
+ function summarizeToolArguments(argsJson) {
748
+ let parsed;
749
+ try {
750
+ parsed = JSON.parse(argsJson);
751
+ } catch {
752
+ return {
753
+ rows: [],
754
+ hidden: 0
755
+ };
756
+ }
757
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
758
+ rows: [],
759
+ hidden: 0
760
+ };
761
+ const entries = Object.entries(parsed);
762
+ return {
763
+ rows: entries.slice(0, 6).map(([key, value]) => ({
764
+ key,
765
+ label: friendlyArgumentName(key),
766
+ value: valueText(value)
767
+ })),
768
+ hidden: Math.max(0, entries.length - 6)
769
+ };
770
+ }
771
+ /** Pretty-printed raw arguments for the technical details. */
772
+ function formatToolArguments(argsJson) {
773
+ try {
774
+ return JSON.stringify(JSON.parse(argsJson), null, 2);
775
+ } catch {
776
+ return argsJson;
777
+ }
778
+ }
779
+
780
+ // src/modules/tool-approval/components/ToolApprovalCard.tsx
781
+ /**
782
+ * One AI assistant action waiting for a decision on the Approvals inbox
783
+ * (BOFF-7521), with the Reject dialog.
784
+ *
785
+ * Navigation v2 (BOFF-11848, FluidGrids only): the card reads "Step: {step}
786
+ * in {workflow}" with an "Open run" link, each proposed call reads "The AI
787
+ * assistant wants to: {action}" with a short summary of its arguments, a
788
+ * time limit says whether it will be approved or rejected automatically, and
789
+ * a request with several calls says the decision covers all of them. The
790
+ * node id, workflow key, run id and raw JSON move under "Show technical
791
+ * details". With the switch off the card is exactly as before.
792
+ */
793
+ function timeAgo(iso, tr) {
794
+ const then = new Date(iso).getTime();
795
+ const diffMs = Date.now() - then;
796
+ const minutes = Math.floor(diffMs / 6e4);
797
+ if (minutes < 1) return tr("fluidgrids.toolApproval.justNow", "just now");
798
+ if (minutes < 60) return tr("fluidgrids.toolApproval.minutesAgo", "{{minutes}}m ago", { minutes });
799
+ const hours = Math.floor(minutes / 60);
800
+ if (hours < 24) return tr("fluidgrids.toolApproval.hoursAgo", "{{hours}}h ago", { hours });
801
+ return tr("fluidgrids.toolApproval.daysAgo", "{{days}}d ago", { days: Math.floor(hours / 24) });
802
+ }
803
+ /** What the time limit does: today's words, or (v2) approve vs reject. */
804
+ function timeoutText(request, tr, plain) {
805
+ const time = new Date(request.timeoutAt ?? "").toLocaleString();
806
+ if (!plain) return tr("fluidgrids.toolApproval.autoResolvesAt", "auto-resolves at {{time}}", { time });
807
+ return request.timeoutAction === "AUTO_APPROVE" ? tr("fluidgrids.toolApproval.autoApprovesAt", "If nobody decides by {{time}}, it will be approved automatically", { time }) : tr("fluidgrids.toolApproval.autoRejectsAt", "If nobody decides by {{time}}, it will be rejected automatically", { time });
808
+ }
809
+ function RejectDialog({ open, onOpenChange, onConfirm, submitting }) {
810
+ const tr = useTr();
811
+ const v2 = useNavigationV2();
812
+ return /* @__PURE__ */ jsx(Dialog, {
813
+ open,
814
+ onOpenChange,
815
+ children: /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.toolApproval.rejectDialogTitle", v2 ? "Don't allow this action" : "Reject tool call") }), v2 && /* @__PURE__ */ jsx(DialogDescription, { children: tr("fluidgrids.toolApproval.rejectDialogDescription", "The AI assistant won't take this action, and your workflow will continue.") })] }), /* @__PURE__ */ jsx(RejectReasonForm, {
816
+ onCancel: () => onOpenChange(false),
817
+ onConfirm,
818
+ submitting
819
+ })] })
820
+ });
821
+ }
822
+ function RejectReasonForm({ onCancel, onConfirm, submitting }) {
823
+ const tr = useTr();
824
+ const v2 = useNavigationV2();
825
+ const [reason, setReason] = useState("");
826
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
827
+ className: "space-y-1.5",
828
+ children: [/* @__PURE__ */ jsx(Label, {
829
+ htmlFor: "reject-reason",
830
+ children: v2 ? tr("fluidgrids.toolApproval.rejectReasonLabelPlain", "Tell the AI assistant why (optional)") : tr("fluidgrids.toolApproval.rejectReasonLabel", "Reason (optional)")
831
+ }), /* @__PURE__ */ jsx(Textarea, {
832
+ id: "reject-reason",
833
+ "data-testid": "tool-approval-reject-reason",
834
+ value: reason,
835
+ onChange: (e) => setReason(e.target.value),
836
+ placeholder: v2 ? void 0 : tr("fluidgrids.toolApproval.rejectReasonPlaceholder", "Why is this call being rejected? The agent sees this.")
837
+ })]
838
+ }), /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
839
+ variant: "outline",
840
+ onClick: onCancel,
841
+ disabled: submitting,
842
+ children: tr("fluidgrids.toolApproval.cancel", "Cancel")
843
+ }), /* @__PURE__ */ jsx(Button$1, {
844
+ variant: "destructive",
845
+ onClick: () => onConfirm(reason),
846
+ disabled: submitting,
847
+ children: submitting ? tr("fluidgrids.toolApproval.rejecting", "Rejecting…") : tr("fluidgrids.toolApproval.rejectConfirm", v2 ? "Don't allow" : "Reject")
848
+ })] })] });
849
+ }
850
+ /** Navigation v2: "The AI assistant wants to: Send email" + its arguments. */
851
+ function PlainToolCall({ call }) {
852
+ const tr = useTr();
853
+ const summary = summarizeToolArguments(call.argsJson);
854
+ return /* @__PURE__ */ jsxs("div", {
855
+ className: "rounded-md border border-border-secondary bg-background-primary p-3",
856
+ "data-testid": "tool-approval-call",
857
+ children: [/* @__PURE__ */ jsx("p", {
858
+ className: "text-sm font-medium text-text-primary",
859
+ children: tr("fluidgrids.toolApproval.wantsTo", "The AI assistant wants to: {{action}}", { action: friendlyToolName(call.name) })
860
+ }), summary.rows.length > 0 && /* @__PURE__ */ jsxs("dl", {
861
+ className: "mt-2 space-y-1 text-xs",
862
+ children: [summary.rows.map((row) => /* @__PURE__ */ jsxs("div", {
863
+ className: "flex flex-wrap gap-x-2",
864
+ children: [/* @__PURE__ */ jsx("dt", {
865
+ className: "font-medium text-text-secondary",
866
+ children: row.label
867
+ }), /* @__PURE__ */ jsx("dd", {
868
+ className: "min-w-0 whitespace-pre-wrap break-words text-text-primary",
869
+ children: row.value
870
+ })]
871
+ }, row.key)), summary.hidden > 0 && /* @__PURE__ */ jsx("p", {
872
+ className: "text-text-secondary",
873
+ children: tr("fluidgrids.toolApproval.moreArguments", "and {{count}} more (see technical details)", { count: summary.hidden })
874
+ })]
875
+ })]
876
+ });
877
+ }
878
+ /** Today's call block: the raw tool name and its JSON arguments. */
879
+ function RawToolCall({ call }) {
880
+ return /* @__PURE__ */ jsxs("div", {
881
+ className: "rounded-md border border-border-secondary bg-background-primary p-3",
882
+ children: [/* @__PURE__ */ jsxs("div", {
883
+ className: "mb-1 flex items-center gap-2",
884
+ children: [/* @__PURE__ */ jsx(Icon, {
885
+ icon: "mdi:function-variant",
886
+ width: 14,
887
+ className: "text-text-secondary"
888
+ }), /* @__PURE__ */ jsx("span", {
889
+ className: "text-sm font-medium text-text-primary",
890
+ children: call.name
891
+ })]
892
+ }), /* @__PURE__ */ jsx("pre", {
893
+ className: "overflow-x-auto whitespace-pre-wrap break-all text-xs text-text-secondary",
894
+ children: formatToolArguments(call.argsJson)
895
+ })]
896
+ });
897
+ }
898
+ function ToolApprovalCard({ request, onDecided, workflowName, stepName }) {
899
+ const tr = useTr();
900
+ const v2 = useNavigationV2();
901
+ const { decideAll, loading } = useDecideToolApproval();
902
+ const [showReject, setShowReject] = useState(false);
903
+ const [nowMs, setNowMs] = useState(() => Date.now());
904
+ const timeoutState = getToolApprovalTimeoutState(request.timeoutAt, nowMs);
905
+ const timeoutDue = timeoutState === "DUE";
906
+ useEffect(() => {
907
+ if (timeoutState !== "ACTIVE") return;
908
+ const interval = setInterval(() => setNowMs(Date.now()), 1e3);
909
+ return () => clearInterval(interval);
910
+ }, [timeoutState]);
911
+ const handleDecide = async (decision, reason) => {
912
+ try {
913
+ await decideAll(request, decision, reason);
914
+ const approved = decision === "APPROVED";
915
+ toast.success(v2 ? approved ? tr("fluidgrids.toolApproval.allowedToast", "Allowed — your workflow is continuing") : tr("fluidgrids.toolApproval.notAllowedToast", "Not allowed — your workflow is continuing") : approved ? tr("fluidgrids.toolApproval.approvedToast", "Tool call approved — run resumed") : tr("fluidgrids.toolApproval.rejectedToast", "Tool call rejected — run resumed"));
916
+ setShowReject(false);
917
+ onDecided();
918
+ } catch (err) {
919
+ const failed = tr("fluidgrids.toolApproval.decisionFailed", "Failed to record the decision");
920
+ toast.error(failed, { description: describeOperationError(err, failed) });
921
+ }
922
+ };
923
+ const deniedTooltip = tr("fluidgrids.toolApproval.decideDeniedTooltip", "You don't have permission to decide tool approvals");
924
+ return /* @__PURE__ */ jsxs(Card, {
925
+ className: "border border-border-secondary bg-background-secondary",
926
+ "data-testid": "tool-approval-card",
927
+ "data-request-id": request.id,
928
+ children: [
929
+ /* @__PURE__ */ jsxs(CardHeader, {
930
+ className: "pb-3",
931
+ children: [/* @__PURE__ */ jsxs("div", {
932
+ className: "flex flex-wrap items-center justify-between gap-2",
933
+ children: [/* @__PURE__ */ jsxs("div", {
934
+ className: "flex items-center gap-2",
935
+ children: [
936
+ /* @__PURE__ */ jsx(Icon, {
937
+ icon: "mdi:robot-outline",
938
+ width: 18,
939
+ className: "text-text-secondary"
940
+ }),
941
+ !v2 && /* @__PURE__ */ jsxs("span", {
942
+ className: "text-sm font-semibold text-text-primary",
943
+ children: [
944
+ tr("fluidgrids.toolApproval.nodeLabel", "Node"),
945
+ ":",
946
+ " ",
947
+ request.nodeId
948
+ ]
949
+ }),
950
+ v2 && /* @__PURE__ */ jsx("span", {
951
+ className: "text-sm font-semibold text-text-primary",
952
+ "data-testid": "approval-source",
953
+ children: approvalSourceText(workflowName ?? request.workflowKey, stepName, tr)
954
+ })
955
+ ]
956
+ }), !v2 && /* @__PURE__ */ jsx(Badge, {
957
+ variant: "outline",
958
+ className: "font-mono text-[10px]",
959
+ children: request.workflowKey
960
+ })]
961
+ }), /* @__PURE__ */ jsxs("p", {
962
+ className: "text-xs text-text-secondary",
963
+ children: [
964
+ tr("fluidgrids.toolApproval.requestedAt", "Requested {{when}}", { when: timeAgo(request.requestedAt, tr) }),
965
+ request.timeoutAt && timeoutDue ? ` · ${tr("fluidgrids.toolApproval.timeoutDue", "timeout reached; automatic decision pending")}` : request.timeoutAt && ` · ${timeoutText(request, tr, v2)}`,
966
+ v2 && /* @__PURE__ */ jsxs(Fragment$1, { children: [" · ", /* @__PURE__ */ jsx(OpenRunLink, { runId: request.runId })] })
967
+ ]
968
+ })]
969
+ }),
970
+ /* @__PURE__ */ jsxs(CardContent, {
971
+ className: "space-y-3",
972
+ children: [
973
+ request.toolCalls.map((call) => v2 ? /* @__PURE__ */ jsx(PlainToolCall, { call }, call.callId) : /* @__PURE__ */ jsx(RawToolCall, { call }, call.callId)),
974
+ v2 && request.toolCalls.length > 1 && /* @__PURE__ */ jsx("p", {
975
+ className: "text-xs text-text-secondary",
976
+ "data-testid": "tool-approval-all-or-nothing",
977
+ children: tr("fluidgrids.toolApproval.allOrNothing", "Your decision applies to all {{count}} actions in this request.", { count: request.toolCalls.length })
978
+ }),
979
+ /* @__PURE__ */ jsxs("div", {
980
+ className: "flex items-center gap-2 pt-1",
981
+ children: [/* @__PURE__ */ jsx(SdkPermissionButton, {
982
+ permission: FLUIDGRIDS_PERMISSIONS.TOOL_APPROVAL_REQUEST_EXECUTE,
983
+ deniedTooltip,
984
+ children: /* @__PURE__ */ jsxs(Button$1, {
985
+ "data-agent": "confirm",
986
+ size: "sm",
987
+ disabled: loading || timeoutDue,
988
+ onClick: () => handleDecide("APPROVED"),
989
+ "data-testid": "tool-approval-approve",
990
+ children: [/* @__PURE__ */ jsx(Icon, {
991
+ icon: "mdi:check",
992
+ className: "mr-1.5",
993
+ width: 16
994
+ }), tr("fluidgrids.toolApproval.approve", "Approve")]
995
+ })
996
+ }), /* @__PURE__ */ jsx(SdkPermissionButton, {
997
+ permission: FLUIDGRIDS_PERMISSIONS.TOOL_APPROVAL_REQUEST_EXECUTE,
998
+ deniedTooltip,
999
+ children: /* @__PURE__ */ jsxs(Button$1, {
1000
+ size: "sm",
1001
+ variant: "destructive",
1002
+ disabled: loading || timeoutDue,
1003
+ onClick: () => setShowReject(true),
1004
+ "data-testid": "tool-approval-reject",
1005
+ children: [/* @__PURE__ */ jsx(Icon, {
1006
+ icon: "mdi:close",
1007
+ className: "mr-1.5",
1008
+ width: 16
1009
+ }), tr("fluidgrids.toolApproval.reject", v2 ? "Don't allow" : "Reject")]
1010
+ })
1011
+ })]
1012
+ }),
1013
+ v2 && /* @__PURE__ */ jsx(ApprovalTechnicalDetails, {
1014
+ rows: approvalTechnicalRows(request, tr),
1015
+ children: /* @__PURE__ */ jsx("div", {
1016
+ className: "mt-2 space-y-2",
1017
+ children: request.toolCalls.map((call) => /* @__PURE__ */ jsx(RawToolCall, { call }, call.callId))
1018
+ })
1019
+ })
1020
+ ]
1021
+ }),
1022
+ /* @__PURE__ */ jsx(RejectDialog, {
1023
+ open: showReject,
1024
+ onOpenChange: setShowReject,
1025
+ submitting: loading,
1026
+ onConfirm: (reason) => handleDecide("REJECTED", reason || void 0)
1027
+ })
1028
+ ]
1029
+ });
1030
+ }
1031
+
1032
+ // src/modules/tool-approval/pages/ApprovalInboxPage.tsx
1033
+ /**
1034
+ * Approvals inbox (BOFF-7521, BOFF-7651): asks from workflows waiting for a
1035
+ * person, and AI Agent tool calls waiting for a decision (see
1036
+ * `components/ToolApprovalCard.tsx`). A request with several calls is
1037
+ * all-or-nothing: the one decision applies to every call in it (there is no
1038
+ * per-call control). Approving/rejecting resumes the underlying run via
1039
+ * `decideToolApproval`, which reuses the exact same WAITING/
1040
+ * `resumeWorkflowRun` mechanism the Wait node uses.
1041
+ *
1042
+ * Navigation v2 (BOFF-11848, FluidGrids only): both kinds of card name their
1043
+ * workflow and step and link the run, the counter says how many wait in all,
1044
+ * the empty state covers both kinds of ask, and a load error is plain words
1045
+ * with a Retry and the server's message under "Show technical details".
1046
+ */
1047
+ var ApprovalInboxPage_exports = /* @__PURE__ */ __exportAll({ ApprovalInboxPage: () => ApprovalInboxPage });
1048
+ /** Navigation v2: a list that failed to load, with Retry. */
1049
+ function InboxLoadError({ message, detail, onRetry, className }) {
1050
+ const tr = useTr();
1051
+ return /* @__PURE__ */ jsxs("div", {
1052
+ className,
1053
+ role: "alert",
1054
+ "data-testid": "approvals-load-error",
1055
+ children: [/* @__PURE__ */ jsxs("div", {
1056
+ className: "flex flex-wrap items-center gap-3",
1057
+ children: [/* @__PURE__ */ jsx("p", {
1058
+ className: "text-sm text-destructive",
1059
+ children: message
1060
+ }), /* @__PURE__ */ jsx(Button$1, {
1061
+ variant: "outline",
1062
+ size: "sm",
1063
+ onClick: onRetry,
1064
+ children: tr("fluidgrids.humanResponse.retry", "Retry")
1065
+ })]
1066
+ }), /* @__PURE__ */ jsx(TechnicalDetails, {
1067
+ className: "mt-1",
1068
+ children: /* @__PURE__ */ jsx("p", {
1069
+ className: "break-words font-mono text-xs text-text-secondary",
1070
+ children: detail
1071
+ })
1072
+ })]
1073
+ });
1074
+ }
1075
+ /** A list that failed to load: today's line, or (v2) plain words with Retry. */
1076
+ function InboxError({ error, plain, oldText, plainText, className, onRetry }) {
1077
+ if (!error) return null;
1078
+ if (!plain) return /* @__PURE__ */ jsx("p", {
1079
+ className,
1080
+ role: "alert",
1081
+ children: oldText(error.message)
1082
+ });
1083
+ return /* @__PURE__ */ jsx(InboxLoadError, {
1084
+ className: "mb-4",
1085
+ message: plainText,
1086
+ detail: error.message,
1087
+ onRetry
1088
+ });
1089
+ }
1090
+ /** "Waiting for a response": the asks for a person, with their pager. */
1091
+ function HumanRequestsSection({ items, totalCount, offset, onOffsetChange, onAnswered, labelsFor }) {
1092
+ const tr = useTr();
1093
+ return /* @__PURE__ */ jsxs("section", {
1094
+ className: "mb-8",
1095
+ "data-testid": "human-requests-section",
1096
+ "data-tour": "fluidgrids-approvals-human",
1097
+ children: [
1098
+ /* @__PURE__ */ jsx("h2", {
1099
+ className: "mb-1 text-lg font-semibold text-text-primary",
1100
+ children: tr("fluidgrids.humanResponse.inboxHeading", "Waiting for a response")
1101
+ }),
1102
+ /* @__PURE__ */ jsx("p", {
1103
+ className: "mb-3 text-xs text-text-secondary",
1104
+ children: tr("fluidgrids.humanResponse.inboxSubtitle", "Workflows paused on a person — approve, decline or answer to let them continue.")
1105
+ }),
1106
+ /* @__PURE__ */ jsx("div", {
1107
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
1108
+ children: items.map((request) => /* @__PURE__ */ jsx(HumanRequestCard, {
1109
+ request,
1110
+ onAnswered,
1111
+ ...labelsFor?.(request, request.nodeId)
1112
+ }, request.id))
1113
+ }),
1114
+ /* @__PURE__ */ jsx(InboxPager, {
1115
+ offset,
1116
+ shown: items.length,
1117
+ total: totalCount,
1118
+ onOffsetChange
1119
+ })
1120
+ ]
1121
+ });
1122
+ }
1123
+ /** A role that may not read tool approvals (BOFF-10104). */
1124
+ function ToolApprovalsNoAccess() {
1125
+ const tr = useTr();
1126
+ return /* @__PURE__ */ jsx(Card, {
1127
+ className: "mb-6 border border-border-secondary bg-background-secondary",
1128
+ "data-testid": "tool-approval-no-access",
1129
+ children: /* @__PURE__ */ jsxs(CardContent, {
1130
+ className: "flex items-start gap-3 py-4",
1131
+ children: [/* @__PURE__ */ jsx(Icon, {
1132
+ icon: "mdi:lock-outline",
1133
+ width: 20,
1134
+ className: "mt-0.5 shrink-0 text-text-secondary"
1135
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
1136
+ className: "text-sm font-medium text-text-primary",
1137
+ children: tr("fluidgrids.toolApproval.noAccessTitle", "You can't review AI Agent tool calls")
1138
+ }), /* @__PURE__ */ jsx("p", {
1139
+ className: "mt-1 text-xs text-text-secondary",
1140
+ children: tr("fluidgrids.toolApproval.noAccessSubtitle", "Your role doesn't include permission to read tool approval requests. Ask a workspace admin if you need access.")
1141
+ })] })]
1142
+ })
1143
+ });
1144
+ }
1145
+ /** Nothing waiting: today's words, or (v2) words for both kinds of ask. */
1146
+ function ApprovalsEmptyState({ plain }) {
1147
+ const tr = useTr();
1148
+ return /* @__PURE__ */ jsx(Card, {
1149
+ className: "border border-border-secondary bg-background-secondary",
1150
+ "data-tour": "fluidgrids-approvals-empty",
1151
+ children: /* @__PURE__ */ jsxs(CardContent, {
1152
+ className: "flex flex-col items-center gap-2 py-10 text-center",
1153
+ children: [
1154
+ /* @__PURE__ */ jsx(Icon, {
1155
+ icon: "mdi:shield-check-outline",
1156
+ width: 32,
1157
+ className: "text-text-secondary"
1158
+ }),
1159
+ /* @__PURE__ */ jsx("p", {
1160
+ className: "text-sm font-medium text-text-primary",
1161
+ children: tr("fluidgrids.toolApproval.emptyTitle", "Nothing waiting for approval")
1162
+ }),
1163
+ /* @__PURE__ */ jsx("p", {
1164
+ className: "text-xs text-text-secondary",
1165
+ children: plain ? tr("fluidgrids.approvals.emptyHint", "When a workflow needs your OK or an answer from you, it shows up here.") : tr("fluidgrids.toolApproval.emptySubtitle", "Runs will appear here when an AI Agent with tool approval enabled proposes a tool call.")
1166
+ })
1167
+ ]
1168
+ })
1169
+ });
1170
+ }
1171
+ /** The AI assistant's actions: the counter, the cards and their pager. */
1172
+ function ToolApprovalsList({ items, totalCount, offset, onOffsetChange, onDecided, labelsFor }) {
1173
+ const tr = useTr();
1174
+ const plain = labelsFor !== void 0;
1175
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1176
+ items.length > 0 && /* @__PURE__ */ jsx("div", {
1177
+ className: "mb-3 text-xs text-text-secondary",
1178
+ "data-testid": "tool-approval-count",
1179
+ children: plain ? tr("fluidgrids.toolApproval.count", "{{count}} waiting for you", {
1180
+ count: totalCount,
1181
+ total: totalCount
1182
+ }) : tr("fluidgrids.toolApproval.count", "{{count}} pending ({{total}} total)", {
1183
+ count: items.length,
1184
+ total: totalCount
1185
+ })
1186
+ }),
1187
+ /* @__PURE__ */ jsx("div", {
1188
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
1189
+ "data-tour": "fluidgrids-approvals-tools",
1190
+ children: items.map((request) => /* @__PURE__ */ jsx(ToolApprovalCard, {
1191
+ request,
1192
+ onDecided,
1193
+ ...labelsFor?.(request, request.nodeId)
1194
+ }, request.id))
1195
+ }),
1196
+ /* @__PURE__ */ jsx(InboxPager, {
1197
+ offset,
1198
+ shown: items.length,
1199
+ total: totalCount,
1200
+ onOffsetChange,
1201
+ "data-testid": "tool-approval-pager"
1202
+ })
1203
+ ] });
1204
+ }
1205
+ const HUMAN_PAGE_SIZE = 50;
1206
+ function ApprovalInboxPage() {
1207
+ const tr = useTr();
1208
+ const v2 = useNavigationV2();
1209
+ const navigate = useNavigate();
1210
+ const { allowed: canReadToolApprovals, isLoading: permissionsLoading } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.TOOL_APPROVAL_REQUEST_READ);
1211
+ const [toolOffset, setToolOffset] = useState(0);
1212
+ const { items, totalCount, loading, error, forbidden, refetch } = usePendingToolApprovals({
1213
+ enabled: canReadToolApprovals,
1214
+ limit: 50,
1215
+ offset: toolOffset
1216
+ });
1217
+ const toolPageOffset = clampPageOffset(toolOffset, totalCount);
1218
+ if (toolPageOffset !== toolOffset && !loading) setToolOffset(toolPageOffset);
1219
+ const toolsForbidden = forbidden && !permissionsLoading;
1220
+ const [humanOffset, setHumanOffset] = useState(0);
1221
+ const human = usePendingHumanRequests({
1222
+ limit: HUMAN_PAGE_SIZE,
1223
+ offset: humanOffset
1224
+ });
1225
+ const humanTotal = human.totalCount;
1226
+ useEffect(() => {
1227
+ if (humanOffset > 0 && humanOffset >= humanTotal) setHumanOffset(Math.max(0, Math.ceil(humanTotal / HUMAN_PAGE_SIZE) - 1) * HUMAN_PAGE_SIZE);
1228
+ }, [humanOffset, humanTotal]);
1229
+ const { allowed: canReadWorkflows } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_READ);
1230
+ const workflowRefs = [...human.items.map((request) => ({ workflowKey: request.workflowKey })), ...(toolsForbidden ? [] : items).map((request) => ({
1231
+ workflowKey: request.workflowKey,
1232
+ workflowId: request.workflowId
1233
+ }))];
1234
+ const workflows = useApprovalWorkflows(workflowRefs, v2 && canReadWorkflows);
1235
+ const labelsFor = (ref, nodeId) => {
1236
+ const labels = approvalLabels(workflows.get(approvalWorkflowRefKey(ref)), ref.workflowKey, nodeId);
1237
+ return {
1238
+ workflowName: labels.workflow,
1239
+ stepName: labels.step
1240
+ };
1241
+ };
1242
+ return /* @__PURE__ */ jsxs("div", {
1243
+ className: "p-4 sm:p-6 bg-background-primary min-h-screen",
1244
+ children: [
1245
+ /* @__PURE__ */ jsxs("div", {
1246
+ className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6",
1247
+ "data-tour": "fluidgrids-approvals-header",
1248
+ children: [/* @__PURE__ */ jsxs("div", { children: [
1249
+ /* @__PURE__ */ jsx("h1", {
1250
+ className: "text-2xl sm:text-3xl font-bold text-text-primary",
1251
+ children: tr("fluidgrids.approvals.pageTitle", "Approvals")
1252
+ }),
1253
+ /* @__PURE__ */ jsx("p", {
1254
+ className: "text-text-secondary mt-1",
1255
+ children: tr("fluidgrids.approvals.pageSubtitle", v2 ? "Requests from your workflows waiting for a person, and actions your AI assistant wants to take" : "Requests from your workflows waiting for a person, and AI Agent tool calls waiting for a decision")
1256
+ }),
1257
+ /* @__PURE__ */ jsx(FluidGridsDocsLink, {
1258
+ topic: "approvals",
1259
+ className: "mt-1"
1260
+ })
1261
+ ] }), /* @__PURE__ */ jsxs(Button$1, {
1262
+ variant: "outline",
1263
+ onClick: () => navigate("../runs"),
1264
+ children: [/* @__PURE__ */ jsx(Icon, {
1265
+ icon: "mdi:arrow-left",
1266
+ className: "mr-2",
1267
+ width: 16
1268
+ }), tr("fluidgrids.toolApproval.backToRuns", "Back to Runs")]
1269
+ })]
1270
+ }),
1271
+ (loading || permissionsLoading) && items.length === 0 && /* @__PURE__ */ jsx("p", {
1272
+ className: "text-text-secondary text-sm",
1273
+ children: tr("fluidgrids.toolApproval.loading", "Loading…")
1274
+ }),
1275
+ /* @__PURE__ */ jsx(InboxError, {
1276
+ error,
1277
+ plain: v2,
1278
+ className: "text-sm text-destructive",
1279
+ oldText: (message) => tr("fluidgrids.toolApproval.loadFailed", "Failed to load pending approvals: {{message}}", { message }),
1280
+ plainText: tr("fluidgrids.toolApproval.loadFailedPlain", "We couldn't load the actions waiting for your OK. Try again in a moment."),
1281
+ onRetry: () => void refetch()
1282
+ }),
1283
+ human.items.length > 0 && /* @__PURE__ */ jsx(HumanRequestsSection, {
1284
+ items: human.items,
1285
+ totalCount: human.totalCount,
1286
+ offset: humanOffset,
1287
+ onOffsetChange: setHumanOffset,
1288
+ onAnswered: human.refetch,
1289
+ labelsFor: v2 ? labelsFor : void 0
1290
+ }),
1291
+ /* @__PURE__ */ jsx(InboxError, {
1292
+ error: human.error,
1293
+ plain: v2,
1294
+ className: "mb-4 text-sm text-destructive",
1295
+ oldText: (message) => tr("fluidgrids.humanResponse.loadFailed", "Failed to load requests waiting for a response: {{message}}", { message }),
1296
+ plainText: tr("fluidgrids.humanResponse.loadFailedPlain", "We couldn't load the requests waiting for your answer. Try again in a moment."),
1297
+ onRetry: () => void human.refetch()
1298
+ }),
1299
+ toolsForbidden && /* @__PURE__ */ jsx(ToolApprovalsNoAccess, {}),
1300
+ !loading && !permissionsLoading && !toolsForbidden && !error && !human.loading && !human.error && items.length === 0 && human.items.length === 0 && /* @__PURE__ */ jsx(ApprovalsEmptyState, { plain: v2 }),
1301
+ !toolsForbidden && /* @__PURE__ */ jsx(ToolApprovalsList, {
1302
+ items,
1303
+ totalCount,
1304
+ offset: toolOffset,
1305
+ onOffsetChange: setToolOffset,
1306
+ onDecided: refetch,
1307
+ labelsFor: v2 ? labelsFor : void 0
1308
+ }),
1309
+ toolsForbidden && /* @__PURE__ */ jsx("div", {
1310
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
1311
+ "data-tour": "fluidgrids-approvals-tools"
1312
+ })
1313
+ ]
1314
+ });
1315
+ }
1316
+
1317
+ export { HumanRequestCard as a, usePendingToolApprovals as i, ApprovalInboxPage_exports as n, useDecideToolApproval as r, ApprovalInboxPage as t };