@burdenoff/fluidgrids-fe-sdk 2026.1111.2 → 2026.1111.4

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 (47) hide show
  1. package/dist/{ExecutionDetailPage-BLQ9mPPO.js → ExecutionDetailPage-CdcutZhM.js} +35 -26
  2. package/dist/{FailureTriagePanel-DquVHzGD.js → FailureTriagePanel-4z3sJbF-.js} +2 -2
  3. package/dist/{FluidGridsDocsLink-iRiufGc2.js → FluidGridsDocsLink-JxkBpOeb.js} +1 -1
  4. package/dist/{FluidGridsProvider-Bve22Hxu.js → FluidGridsProvider-D4VBLmsU.js} +3 -3
  5. package/dist/{HistoryPage-CCVLBD8a.js → HistoryPage-DsuGx50x.js} +5 -5
  6. package/dist/{InsightsPage-BCRRKaja.js → InsightsPage-DPSE3P7m.js} +5 -5
  7. package/dist/{ManagedAiPolicyAuthority-PhyUARha.js → ManagedAiPolicyAuthority-Dc9UeQPh.js} +300 -74
  8. package/dist/{MissingNodesInstallModal-CkbBf648.js → MissingNodesInstallModal-DLaS7YqW.js} +2 -2
  9. package/dist/{NodesPage-XglgbnFQ.js → NodesPage-C2woL-6F.js} +3 -3
  10. package/dist/{PublishCustomNodeAction-CtRHgitG.js → PublishCustomNodeAction-ByxKmyOF.js} +2 -2
  11. package/dist/{TemplateConfigurationModal-DPe69XFz.js → TemplateConfigurationModal-Cc9ULJd8.js} +3 -3
  12. package/dist/{TemplateDetailPanel-ahqcAMmr.js → TemplateDetailPanel-CnlspQbn.js} +3 -3
  13. package/dist/{TemplatesPage-CRIn51Qa.js → TemplatesPage-CZdxYIcf.js} +7 -7
  14. package/dist/{TranslationBridge-CX0OMnyn.js → TranslationBridge-Da-TPgQm.js} +6 -1
  15. package/dist/{VersionDiffDialog-BejQlKec.js → VersionDiffDialog-B6JNtNM1.js} +3 -3
  16. package/dist/{WorkflowProposalCard-aoR_tT2f.js → WorkflowProposalCard-Ci5kwjR4.js} +2 -2
  17. package/dist/{WorkflowSearchRedirect-5x2HX5tx.js → WorkflowSearchRedirect-CoCTVuHn.js} +13 -13
  18. package/dist/{WorkflowShareDialog-BONVJpp_.js → WorkflowShareDialog-DknKAi9x.js} +3 -3
  19. package/dist/{WorkflowsList-CDPqtaa3.js → WorkflowsList-BzCLsBR-.js} +8 -8
  20. package/dist/{agent-form-m8tkBULw.js → agent-form-hb7BR-JZ.js} +1 -1
  21. package/dist/{agentTools-DxuakWxT.js → agentTools-NEBmfRWF.js} +3 -3
  22. package/dist/{codec-client-aN37SXBf.js → codec-client-C3LkdZvc.js} +2 -2
  23. package/dist/{definition-canvas-D9cWIGEM.js → definition-canvas--3MPHoP-.js} +1 -1
  24. package/dist/executions.js +1 -1
  25. package/dist/{flow-builder-CmJi6XWS.js → flow-builder-28Hp8NgO.js} +25 -25
  26. package/dist/history.js +1 -1
  27. package/dist/{index-CpEB91Ys.d.ts → index-lX6U7gi1.d.ts} +35 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +27 -27
  30. package/dist/{node-detail-view-CAUbejnM.js → node-detail-view-CVkO4pda.js} +8 -8
  31. package/dist/{node-endpoint-configs-BgZARXHI.js → node-endpoint-configs-DS59aLlH.js} +297 -1
  32. package/dist/{node-operation-selection-61d6U9qo.js → node-operation-selection--oy-yEq_.js} +1 -1
  33. package/dist/nodes.js +1 -1
  34. package/dist/observability.d.ts +1 -1
  35. package/dist/observability.js +1 -1
  36. package/dist/{quick-config-panel-BxM7KKaU.js → quick-config-panel-CpjVDTyF.js} +4 -4
  37. package/dist/settings.js +1 -1
  38. package/dist/templates.js +1 -1
  39. package/dist/{useAgentDefinitions-BKimckKY.js → useAgentDefinitions-SRq-nq4y.js} +1 -1
  40. package/dist/{useExecutions-BIrUuMlR.js → useExecutions-BdDmxTNK.js} +3 -3
  41. package/dist/{useObservability-cCsmALjS.js → useObservability-CrZ-YuBS.js} +3 -3
  42. package/dist/{useRunHistory-DG0Nfolg.js → useRunHistory-BVxQmQ6g.js} +3 -3
  43. package/dist/{useWorkflows-CQbVEHc0.js → useWorkflows-BfDQmAeS.js} +3 -3
  44. package/dist/workflow-builder.js +3 -3
  45. package/dist/workflow-preview.js +3 -3
  46. package/dist/workflows-list.js +1 -1
  47. package/package.json +1 -1
@@ -1,20 +1,20 @@
1
1
  "use client";
2
2
  import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
- import { $ as DurableParityRunFieldsDocument, B as DeleteTaskQueueDocument, D as CreateWorkerTokenDocument, E as CreateTaskQueueDocument, En as SetWorkflowRunVersioningOverrideDocument, Fi as throwIfResultError, Gr as WorkflowRunPendingDocument, Jr as WorkflowRunsCountDocument, Mi as describeOperationError, Rn as TaskQueueStatsDocument, V as DeleteWorkerTokenDocument, Vr as WorkflowRunHierarchyDocument, Wr as WorkflowRunMetadataDocument, Xi as DEFAULT_EXECUTIONS_FILTER, Zi as useExecutionsStore, _n as RunLogsDocument, bi as getFluidGridsClient, cr as WorkerTokensDocument, ct as ExplainRunDocument, er as UpdateTaskQueueDocument, hi as WorkflowWorkersDocument, hn as RunLogAppendedDocument, hr as WorkflowBatchOperationUpdatedDocument, on as RotateWorkerTokenDocument, sn as RunAiUsageDocument, wn as SetWorkerTokenConnectionAllowlistDocument, xi as getFluidGridsClientSafe, zi as getFluidGridsConfig, zn as TaskQueuesDocument } from "./node-endpoint-configs-BgZARXHI.js";
4
- import { $t as runDurationMs, Hn as useDurableFunctionNodesEnabled, Pn as runSignalTargets, Qt as nodeStateDurationMs, Un as useDurableParityEnabled, Yn as useWorkflowTestsEnabled, Zt as formatElapsedMs, jn as durableInterfaceOf, kn as canSignalRun, qn as usePhaseCFlags } from "./FluidGridsProvider-Bve22Hxu.js";
5
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
6
- import { h as useWorkflowDefinitions, v as useWorkflowVersions } from "./useWorkflows-CQbVEHc0.js";
3
+ import { $ as DurableParityRunFieldsDocument, $i as useExecutionsStore, B as DeleteTaskQueueDocument, Ci as getFluidGridsClientSafe, D as CreateWorkerTokenDocument, E as CreateTaskQueueDocument, En as SetWorkflowRunVersioningOverrideDocument, Kr as WorkflowRunMetadataDocument, Li as throwIfResultError, Pi as describeOperationError, Qi as DEFAULT_EXECUTIONS_FILTER, Rn as TaskQueueStatsDocument, Si as getFluidGridsClient, Ur as WorkflowRunHierarchyDocument, V as DeleteWorkerTokenDocument, Vi as getFluidGridsConfig, Xr as WorkflowRunsCountDocument, _i as WorkflowWorkersDocument, _n as RunLogsDocument, ct as ExplainRunDocument, er as UpdateTaskQueueDocument, gr as WorkflowBatchOperationUpdatedDocument, hn as RunLogAppendedDocument, lr as WorkerTokensDocument, on as RotateWorkerTokenDocument, qr as WorkflowRunPendingDocument, sn as RunAiUsageDocument, wn as SetWorkerTokenConnectionAllowlistDocument, zn as TaskQueuesDocument } from "./node-endpoint-configs-DS59aLlH.js";
4
+ import { $t as runDurationMs, Hn as useDurableFunctionNodesEnabled, Pn as runSignalTargets, Qt as nodeStateDurationMs, Un as useDurableParityEnabled, Yn as useWorkflowTestsEnabled, Zt as formatElapsedMs, jn as durableInterfaceOf, kn as canSignalRun, qn as usePhaseCFlags } from "./FluidGridsProvider-D4VBLmsU.js";
5
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
6
+ import { h as useWorkflowDefinitions, v as useWorkflowVersions } from "./useWorkflows-BfDQmAeS.js";
7
7
  import { KF as WorkflowUpdateWaitFor, OF as WorkflowQueryRejectCondition, Qu as ErrorType, dF as WorkflowBatchOperationType, fI as useFluidGridsPermission, kF as WorkflowResetType, lI as SdkPermissionButton, oI as FLUIDGRIDS_PERMISSIONS, pP as VersioningBehavior } from "./graphql-types-CulUbQWN.js";
8
- import { _ as statusCountFilter, c as OPERATORS_BY_TYPE, d as STATUS_COUNT_GROUP_BY, f as callbackTone, g as shortRunId, h as runChainLinks, i as useExecutionStats, l as buildSearchAttributeFilter, m as parseMetadataDefinition, n as useExecution, o as useExecutionsWithStore, p as overrideRequestOf, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, v as statusCountsOf, y as validateDateRange } from "./useExecutions-BIrUuMlR.js";
8
+ import { _ as statusCountFilter, c as OPERATORS_BY_TYPE, d as STATUS_COUNT_GROUP_BY, f as callbackTone, g as shortRunId, h as runChainLinks, i as useExecutionStats, l as buildSearchAttributeFilter, m as parseMetadataDefinition, n as useExecution, o as useExecutionsWithStore, p as overrideRequestOf, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, v as statusCountsOf, y as validateDateRange } from "./useExecutions-BdDmxTNK.js";
9
9
  import { n as useFluidGridsPageControls } from "./pageControlsBridge-0pX9s-3k.js";
10
10
  import { t as executionsControls } from "./pageControlSpecs-DZJ9K37m.js";
11
- import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-iRiufGc2.js";
12
- import { $ as startBatchTerminate, A as fetchBatchOperation, B as jsonArg, F as fetchRunControlFields, G as resetRun, I as fetchRunHistoryArchive, K as resetRunToLastCleanTask, L as fetchRunUpdate, N as fetchCodecServerSettings, O as failActivityById, P as fetchPauseRules, Q as startBatchOperation, R as fetchSearchAttributes, U as queryRun, V as pauseActivities, W as resetActivities, X as sendRunUpdate, Z as startBatchExport, _ as useResource, at as readTyped, c as FieldError, d as NativeSelect, f as RUN_CONTROL_DIALOG_STYLE, g as parseJsonObjectInput, h as parseJsonInput, it as upsertRunSearchAttributes, j as fetchBatchOperations, l as JsonBlock, m as newRunControlId, ot as writeTyped, p as StatusPill, q as revokeSignalToken, rt as updateActivityOptions, s as isAgentNodeType, tt as unpauseActivities, u as JsonField, w as createSignalToken, x as completeActivityById, y as cancelBatchOperation, z as fetchSignalTokens } from "./agent-form-m8tkBULw.js";
13
- import { C as useAvailableTags, _ as buildNodeDisplayMap, b as EntityTagPicker, n as useWorkflowProposal, o as canSaveRunAsTestCase, r as saveRunAsTestCase, t as WorkflowProposalCard, v as formatRunVersion, y as nodeDisplayTitle } from "./WorkflowProposalCard-aoR_tT2f.js";
11
+ import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
12
+ import { $ as startBatchTerminate, A as fetchBatchOperation, B as jsonArg, F as fetchRunControlFields, G as resetRun, I as fetchRunHistoryArchive, K as resetRunToLastCleanTask, L as fetchRunUpdate, N as fetchCodecServerSettings, O as failActivityById, P as fetchPauseRules, Q as startBatchOperation, R as fetchSearchAttributes, U as queryRun, V as pauseActivities, W as resetActivities, X as sendRunUpdate, Z as startBatchExport, _ as useResource, at as readTyped, c as FieldError, d as NativeSelect, f as RUN_CONTROL_DIALOG_STYLE, g as parseJsonObjectInput, h as parseJsonInput, it as upsertRunSearchAttributes, j as fetchBatchOperations, l as JsonBlock, m as newRunControlId, ot as writeTyped, p as StatusPill, q as revokeSignalToken, rt as updateActivityOptions, s as isAgentNodeType, tt as unpauseActivities, u as JsonField, w as createSignalToken, x as completeActivityById, y as cancelBatchOperation, z as fetchSignalTokens } from "./agent-form-hb7BR-JZ.js";
13
+ import { C as useAvailableTags, _ as buildNodeDisplayMap, b as EntityTagPicker, n as useWorkflowProposal, o as canSaveRunAsTestCase, r as saveRunAsTestCase, t as WorkflowProposalCard, v as formatRunVersion, y as nodeDisplayTitle } from "./WorkflowProposalCard-Ci5kwjR4.js";
14
14
  import { t as AiCreditBalanceSlot } from "./aiCreditsBridge-BuE92tKo.js";
15
- import { a as fetchRunFailureClusters, i as dismissRunFailureCluster, o as runListFilterFromSearch, r as OPEN_STATUSES, s as runListLinkKey, t as BUCKET_LABELS } from "./FailureTriagePanel-DquVHzGD.js";
16
- import { a as encodedPayloadBytes, f as EffectBadge, h as useRunPolicyDecisions, i as decodePayloads, l as formatBudgetUsd, n as checkCodecServerUrl, o as isEncodedPayload, r as containsEncodedPayload, t as CodecServerError } from "./codec-client-aN37SXBf.js";
17
- import { a as useDurableRunFields, i as useDeterminismStatus, n as SignalRunDialog, o as useRunStackTrace, r as terminateDurableRun, s as RunHistoryView, t as useRunHistory } from "./useRunHistory-DG0Nfolg.js";
15
+ import { a as fetchRunFailureClusters, i as dismissRunFailureCluster, o as runListFilterFromSearch, r as OPEN_STATUSES, s as runListLinkKey, t as BUCKET_LABELS } from "./FailureTriagePanel-4z3sJbF-.js";
16
+ import { a as encodedPayloadBytes, f as EffectBadge, h as useRunPolicyDecisions, i as decodePayloads, l as formatBudgetUsd, n as checkCodecServerUrl, o as isEncodedPayload, r as containsEncodedPayload, t as CodecServerError } from "./codec-client-C3LkdZvc.js";
17
+ import { a as useDurableRunFields, i as useDeterminismStatus, n as SignalRunDialog, o as useRunStackTrace, r as terminateDurableRun, s as RunHistoryView, t as useRunHistory } from "./useRunHistory-BVxQmQ6g.js";
18
18
  import { a as formatTokens, o as modelLabel, r as aiCost } from "./ai-usage-CK61EZWc.js";
19
19
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
20
  import { Link, useNavigate, useParams, useSearchParams } from "react-router";
@@ -5076,58 +5076,67 @@ function RunDataPrunedNotice({ dataPrunedAt }) {
5076
5076
  function isRecord$1(value) {
5077
5077
  return typeof value === "object" && value !== null && !Array.isArray(value);
5078
5078
  }
5079
+ function isRunQueueScope(value) {
5080
+ return value === "WORKFLOW" || value === "WORKSPACE";
5081
+ }
5079
5082
  /** The queue marker on a run's context, or null when there is none or it is malformed. */
5080
5083
  function readRunQueueMarker(context) {
5081
5084
  if (!isRecord$1(context)) return null;
5082
5085
  const marker = context.concurrency;
5083
5086
  if (!isRecord$1(marker)) return null;
5084
- const { queuedAt, checkedAt, limit, active, position } = marker;
5085
- if (typeof queuedAt !== "string" || typeof checkedAt !== "string" || typeof limit !== "number" || typeof active !== "number" || typeof position !== "number") return null;
5087
+ const { queuedAt, checkedAt, limit, active, position, scope } = marker;
5088
+ if (typeof queuedAt !== "string" || typeof checkedAt !== "string" || typeof limit !== "number" || typeof active !== "number" || typeof position !== "number" || scope !== void 0 && !isRunQueueScope(scope)) return null;
5086
5089
  return {
5087
5090
  queuedAt,
5088
5091
  checkedAt,
5089
5092
  limit,
5090
5093
  active,
5091
- position
5094
+ position,
5095
+ scope: isRunQueueScope(scope) ? scope : "WORKFLOW"
5092
5096
  };
5093
5097
  }
5094
5098
 
5095
5099
  // src/modules/executions/components/RunQueuedBanner.tsx
5096
5100
  /**
5097
- * Run detail: why a PENDING run has not started (BOFF-7647).
5101
+ * Run detail: why a PENDING run has not started (BOFF-7647, BOFF-9949).
5098
5102
  *
5099
- * A workflow's "Max parallel executions" setting holds extra production runs
5100
- * in a queue instead of failing them; they start in order as slots free up.
5101
- * The worker records the wait on the run as `context.concurrency`. Renders
5102
- * nothing for any other run.
5103
+ * A workflow's "Max parallel executions" setting, and the workspace's
5104
+ * concurrent-run cap from its plan, hold extra production runs in a queue
5105
+ * instead of failing them; they start in order as slots free up. The worker
5106
+ * records the wait on the run as `context.concurrency`, with the limit it
5107
+ * waits behind. Renders nothing for any other run.
5103
5108
  */
5104
5109
  function RunQueuedBanner({ status, context }) {
5105
5110
  const tr = useTr();
5106
5111
  if (status !== "PENDING") return null;
5107
5112
  const marker = readRunQueueMarker(context);
5108
5113
  if (!marker) return null;
5114
+ const numbers = {
5115
+ limit: String(marker.limit),
5116
+ active: String(marker.active),
5117
+ position: String(marker.position)
5118
+ };
5119
+ const since = new Date(marker.queuedAt).toLocaleString();
5120
+ const workspace = marker.scope === "WORKSPACE";
5109
5121
  return /* @__PURE__ */ jsxs("div", {
5110
5122
  className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary p-4",
5111
5123
  "data-testid": "run-queued-banner",
5124
+ "data-queue-scope": marker.scope,
5112
5125
  children: [
5113
5126
  /* @__PURE__ */ jsxs("div", {
5114
5127
  className: "flex items-center gap-2 text-sm font-medium text-text-primary",
5115
5128
  children: [/* @__PURE__ */ jsx(Icon, {
5116
5129
  icon: "mdi:tray-full",
5117
5130
  width: 18
5118
- }), tr("fluidgrids.executions.queued.heading", "Queued")]
5131
+ }), workspace ? tr("fluidgrids.executions.queued.workspaceHeading", "Waiting: workspace concurrency limit") : tr("fluidgrids.executions.queued.heading", "Queued")]
5119
5132
  }),
5120
5133
  /* @__PURE__ */ jsx("p", {
5121
5134
  className: "mt-1 text-sm text-text-secondary",
5122
- children: tr("fluidgrids.executions.queued.body", "This workflow allows {{limit}} runs at a time and {{active}} are in progress. This run is number {{position}} in line and starts automatically when a slot frees up.", {
5123
- limit: String(marker.limit),
5124
- active: String(marker.active),
5125
- position: String(marker.position)
5126
- })
5135
+ children: workspace ? tr("fluidgrids.executions.queued.workspaceBody", "This workspace allows {{limit}} runs at a time across all its workflows and {{active}} are in progress. This run is number {{position}} in line and starts automatically when a slot frees up.", numbers) : tr("fluidgrids.executions.queued.body", "This workflow allows {{limit}} runs at a time and {{active}} are in progress. This run is number {{position}} in line and starts automatically when a slot frees up.", numbers)
5127
5136
  }),
5128
5137
  /* @__PURE__ */ jsx("p", {
5129
5138
  className: "mt-1 text-xs text-text-tertiary",
5130
- children: tr("fluidgrids.executions.queued.since", "Queued since {{time}} · change the limit in the workflow's Settings (Max parallel executions)", { time: new Date(marker.queuedAt).toLocaleString() })
5139
+ children: workspace ? tr("fluidgrids.executions.queued.workspaceSince", "Queued since {{time}} · the limit comes from your workspace's plan", { time: since }) : tr("fluidgrids.executions.queued.since", "Queued since {{time}} · change the limit in the workflow's Settings (Max parallel executions)", { time: since })
5131
5140
  })
5132
5141
  ]
5133
5142
  });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { Cn as SetRunFailureTriageEnabledDocument, Fi as throwIfResultError, X as DismissRunFailureClusterDocument, bi as getFluidGridsClient, dn as RunFailureClustersDocument, fn as RunFailureTriageSettingsDocument, ft as FgTriageFailedRunsDocument } from "./node-endpoint-configs-BgZARXHI.js";
3
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
2
+ import { Cn as SetRunFailureTriageEnabledDocument, Li as throwIfResultError, Si as getFluidGridsClient, X as DismissRunFailureClusterDocument, dn as RunFailureClustersDocument, fn as RunFailureTriageSettingsDocument, ft as FgTriageFailedRunsDocument } from "./node-endpoint-configs-DS59aLlH.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
4
  import { Qu as ErrorType, cD as RunFailureClusterStatus } from "./graphql-types-CulUbQWN.js";
5
5
  import { useCallback, useEffect, useState } from "react";
6
6
  import { Icon } from "@iconify/react";
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
2
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
3
3
  import { cn } from "@burdenoff/fe-libs/utils";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { DocsLink } from "@burdenoff/fe-libs/shared/components";
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
- import { Ai as emitOperationErrorEvent, Ci as resetFluidGridsClient, Ei as createFluidGridsTokenRefreshLink, Et as NodeManifestDocument, F as CreateWorkflowVersionDocument, Fi as throwIfResultError, Hi as resetFluidGridsExecutionState, Hn as TriggerWorkflowDocument, Ii as createLiveTokenReader, Mi as describeOperationError, O as CreateWorkflowDefinitionDocument, Ti as waitForFluidGridsClient, Ui as setFluidGridsConfig, Vi as resetFluidGridsConfig, Wi as validateTrustedOrigin, bi as getFluidGridsClient, br as WorkflowDefinitionDocument, it as ExecutionDetailDocument, ji as emitRunCompletedOnce, ki as emitFluidGridsEvent, nt as EventRegistrationsDocument, pn as RunFromNodeDocument, rt as ExecuteStepDocument, t as fetchAllNodeEndpointConfigs, tr as UpdateWorkflowDefinitionDocument, wi as setFluidGridsClient, xi as getFluidGridsClientSafe, yi as createFluidGridsClient, zi as getFluidGridsConfig, zr as WorkflowRunDocument } from "./node-endpoint-configs-BgZARXHI.js";
4
- import { A as nodeAccent, C as nodeAdaptersOf, D as NodeCard, E as resolveOutputHandles, H as useFlowStore, I as partitionStickyNotes, L as resolveStickyColor, N as DEFAULT_STICKY_COLOR, O as NodeCardBody, P as STICKY_COLORS, R as stickySurfaceStyle, T as resolveInputHandles, V as STICKY_NOTE_TYPE, W as isNodeTrigger, _ as LogicNodeCard, b as alignPlainOutputEdges, i as WAIT_FOR_SIGNAL_NODE_TYPE, j as NodeTypeIcon, k as NodeCardHeader, l as interfaceFields, m as readSignalName, n as synchronizeOperationUpdate, p as readCodeWorkflowState, r as CODE_WORKFLOW_NODE_TYPE, t as resolveOperationName, u as interfaceHandlers, v as InputHandles, w as outputHandlesSpacerHeight, x as hasNodeManifest, y as OutputHandles } from "./node-operation-selection-61d6U9qo.js";
5
- import { n as FluidGridsTranslationProvider, r as useTr, t as ENGLISH_FALLBACK_TRANSLATOR } from "./TranslationBridge-CX0OMnyn.js";
3
+ import { Ci as getFluidGridsClientSafe, Di as waitForFluidGridsClient, Ei as setFluidGridsClient, Et as NodeManifestDocument, F as CreateWorkflowVersionDocument, Gi as setFluidGridsConfig, Hn as TriggerWorkflowDocument, Ki as validateTrustedOrigin, Li as throwIfResultError, Mi as emitOperationErrorEvent, Ni as emitRunCompletedOnce, O as CreateWorkflowDefinitionDocument, Oi as createFluidGridsTokenRefreshLink, Pi as describeOperationError, Ri as createLiveTokenReader, Si as getFluidGridsClient, Ti as resetFluidGridsClient, Ui as resetFluidGridsConfig, Vi as getFluidGridsConfig, Vr as WorkflowRunDocument, Wi as resetFluidGridsExecutionState, it as ExecutionDetailDocument, ji as emitFluidGridsEvent, nt as EventRegistrationsDocument, pn as RunFromNodeDocument, rt as ExecuteStepDocument, t as fetchAllNodeEndpointConfigs, tr as UpdateWorkflowDefinitionDocument, xi as createFluidGridsClient, xr as WorkflowDefinitionDocument } from "./node-endpoint-configs-DS59aLlH.js";
4
+ import { A as nodeAccent, C as nodeAdaptersOf, D as NodeCard, E as resolveOutputHandles, H as useFlowStore, I as partitionStickyNotes, L as resolveStickyColor, N as DEFAULT_STICKY_COLOR, O as NodeCardBody, P as STICKY_COLORS, R as stickySurfaceStyle, T as resolveInputHandles, V as STICKY_NOTE_TYPE, W as isNodeTrigger, _ as LogicNodeCard, b as alignPlainOutputEdges, i as WAIT_FOR_SIGNAL_NODE_TYPE, j as NodeTypeIcon, k as NodeCardHeader, l as interfaceFields, m as readSignalName, n as synchronizeOperationUpdate, p as readCodeWorkflowState, r as CODE_WORKFLOW_NODE_TYPE, t as resolveOperationName, u as interfaceHandlers, v as InputHandles, w as outputHandlesSpacerHeight, x as hasNodeManifest, y as OutputHandles } from "./node-operation-selection--oy-yEq_.js";
5
+ import { n as FluidGridsTranslationProvider, r as useTr, t as ENGLISH_FALLBACK_TRANSLATOR } from "./TranslationBridge-Da-TPgQm.js";
6
6
  import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
7
7
  import { useNavigate, useParams } from "react-router";
8
8
  import { ApolloProvider } from "@apollo/client/react";
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { d as useRestoreWorkflowVersion, h as useWorkflowDefinitions } from "./useWorkflows-CQbVEHc0.js";
3
- import { i as versionsOfWorkflow, t as VersionDiffDialog } from "./VersionDiffDialog-BejQlKec.js";
4
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
5
- import { zi as getFluidGridsConfig } from "./node-endpoint-configs-BgZARXHI.js";
2
+ import { d as useRestoreWorkflowVersion, h as useWorkflowDefinitions } from "./useWorkflows-BfDQmAeS.js";
3
+ import { i as versionsOfWorkflow, t as VersionDiffDialog } from "./VersionDiffDialog-B6JNtNM1.js";
4
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
5
+ import { Vi as getFluidGridsConfig } from "./node-endpoint-configs-DS59aLlH.js";
6
6
  import { n as useFluidGridsPageControls } from "./pageControlsBridge-0pX9s-3k.js";
7
7
  import { n as historyControls } from "./pageControlSpecs-DZJ9K37m.js";
8
- import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-iRiufGc2.js";
8
+ import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
9
9
  import { useCallback, useMemo, useState } from "react";
10
10
  import { useNavigate } from "react-router";
11
11
  import { Icon } from "@iconify/react";
@@ -1,12 +1,12 @@
1
1
  "use client";
2
- import { Fi as throwIfResultError, Ir as WorkflowProposalDocument, Rt as ProposeOptimisationDocument, S as ContinuousOptimisationSettingsDocument, Sn as SetContinuousOptimisationEnabledDocument, Y as DismissOptimisationSuggestionDocument, bi as getFluidGridsClient, dt as FgRunOptimisationScanDocument, gi as WorkspaceAiUsageDocument, jt as OptimisationSuggestionsDocument, kn as SnoozeOptimisationSuggestionDocument, tt as ErrorAnalyticsDocument } from "./node-endpoint-configs-BgZARXHI.js";
3
- import { Vn as useContinuousOptimisationEnabled } from "./FluidGridsProvider-Bve22Hxu.js";
4
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
2
+ import { Li as throwIfResultError, Rr as WorkflowProposalDocument, Rt as ProposeOptimisationDocument, S as ContinuousOptimisationSettingsDocument, Si as getFluidGridsClient, Sn as SetContinuousOptimisationEnabledDocument, Y as DismissOptimisationSuggestionDocument, dt as FgRunOptimisationScanDocument, jt as OptimisationSuggestionsDocument, kn as SnoozeOptimisationSuggestionDocument, tt as ErrorAnalyticsDocument, vi as WorkspaceAiUsageDocument } from "./node-endpoint-configs-DS59aLlH.js";
3
+ import { Vn as useContinuousOptimisationEnabled } from "./FluidGridsProvider-D4VBLmsU.js";
4
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
5
5
  import { BC as OptimisationSuggestionStatus, RC as OptimisationDismissReason, fI as useFluidGridsPermission, lI as SdkPermissionButton, oI as FLUIDGRIDS_PERMISSIONS, zC as OptimisationKind } from "./graphql-types-CulUbQWN.js";
6
6
  import { t as proposalCanvasPath } from "./useRequestedProposal-B7uPXfZB.js";
7
- import { c as runListPath, l as utcDay, n as FailureTriagePanel } from "./FailureTriagePanel-DquVHzGD.js";
7
+ import { c as runListPath, l as utcDay, n as FailureTriagePanel } from "./FailureTriagePanel-4z3sJbF-.js";
8
8
  import { a as formatTokens, c as DISMISS_REASONS, d as monthlySavingLabel, f as shortRunId, i as aiSpendPeriod, l as evidenceFacts, r as aiCost, s as runsOfWorkflowPath, t as AI_SPEND_RANGE_DAYS, u as evidenceWindowDays } from "./ai-usage-CK61EZWc.js";
9
- import { n as useDataSinks, o as useRunReplayActions, r as useInsightsData, t as useDataSinkData } from "./useObservability-cCsmALjS.js";
9
+ import { n as useDataSinks, o as useRunReplayActions, r as useInsightsData, t as useDataSinkData } from "./useObservability-CrZ-YuBS.js";
10
10
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11
11
  import { Link, useNavigate } from "react-router";
12
12
  import { Icon } from "@iconify/react";
@@ -1,14 +1,14 @@
1
1
  "use client";
2
- import { $n as UpdateManagedAiWorkspacePolicyDocument, Fi as throwIfResultError, Mi as describeOperationError, Q as DurableHistoryCapsDocument, Qn as UpdateDurableHistoryCapsDocument, St as ManagedAiWorkspacePolicyDocument, bi as getFluidGridsClient, zi as getFluidGridsConfig } from "./node-endpoint-configs-BgZARXHI.js";
3
- import { In as FLUIDGRIDS_FLAGS, Kt as findSaveConflict, Un as useDurableParityEnabled, Wn as useFluidGridsFlag, or as getGlobalStoreClient, qn as usePhaseCFlags, wt as useSaveStatusStore } from "./FluidGridsProvider-Bve22Hxu.js";
4
- import { H as useFlowStore } from "./node-operation-selection-61d6U9qo.js";
5
- import { r as useTr } from "./TranslationBridge-CX0OMnyn.js";
6
- import { Wt as useWorkflowDefinitionQuery, a as useConnections, n as useAgentDefinitions, qt as useWorkflowDefinitionsQuery, zt as useUpdateWorkflowDefinitionMutation } from "./useAgentDefinitions-BKimckKY.js";
2
+ import { $n as UpdateManagedAiWorkspacePolicyDocument, Li as throwIfResultError, Pi as describeOperationError, Pr as WorkflowNotificationSettingsDocument, Q as DurableHistoryCapsDocument, Qn as UpdateDurableHistoryCapsDocument, Si as getFluidGridsClient, St as ManagedAiWorkspacePolicyDocument, Vi as getFluidGridsConfig, rr as UpdateWorkflowNotificationSettingsDocument } from "./node-endpoint-configs-DS59aLlH.js";
3
+ import { In as FLUIDGRIDS_FLAGS, Kt as findSaveConflict, Un as useDurableParityEnabled, Wn as useFluidGridsFlag, or as getGlobalStoreClient, qn as usePhaseCFlags, wt as useSaveStatusStore } from "./FluidGridsProvider-D4VBLmsU.js";
4
+ import { H as useFlowStore } from "./node-operation-selection--oy-yEq_.js";
5
+ 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-SRq-nq4y.js";
7
7
  import { LF as WorkflowSearchAttributeType, fI as useFluidGridsPermission, oI as FLUIDGRIDS_PERMISSIONS } from "./graphql-types-CulUbQWN.js";
8
- import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-iRiufGc2.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-m8tkBULw.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-aN37SXBf.js";
11
- import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-cCsmALjS.js";
8
+ 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-hb7BR-JZ.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-C3LkdZvc.js";
11
+ import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-CrZ-YuBS.js";
12
12
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
13
13
  import { useNavigate, useParams } from "react-router";
14
14
  import { Icon } from "@iconify/react";
@@ -2470,6 +2470,296 @@ function RunDataTab() {
2470
2470
  });
2471
2471
  }
2472
2472
 
2473
+ // src/modules/settings/hooks/useWorkflowNotificationSettings.ts
2474
+ /**
2475
+ * A workflow's notification preferences (G08) for Settings → Notifications
2476
+ * (BOFF-9974): which events notify the workflow's person in the app's bell
2477
+ * and, unless switched off, by push to their devices, and how often.
2478
+ *
2479
+ * Read with `workflowNotificationSettings` (the defaults when nobody saved
2480
+ * any) and saved with `updateWorkflowNotificationSettings`, a partial update.
2481
+ */
2482
+ /** The throttle window the service accepts, in minutes (0 sends every one). */
2483
+ const NOTIFICATION_THROTTLE_RANGE = {
2484
+ min: 0,
2485
+ max: 1440
2486
+ };
2487
+ async function fetchWorkflowNotificationSettings(workflowKey) {
2488
+ const result = await getFluidGridsClient().query({
2489
+ query: WorkflowNotificationSettingsDocument,
2490
+ variables: { workflowKey },
2491
+ fetchPolicy: "network-only"
2492
+ });
2493
+ throwIfResultError(result);
2494
+ return result.data?.workflowNotificationSettings ?? null;
2495
+ }
2496
+ async function saveWorkflowNotificationSettings(workflowKey, changes) {
2497
+ const result = await getFluidGridsClient().mutate({
2498
+ mutation: UpdateWorkflowNotificationSettingsDocument,
2499
+ variables: { input: {
2500
+ workflowKey,
2501
+ ...changes
2502
+ } }
2503
+ });
2504
+ throwIfResultError(result);
2505
+ const saved = result.data?.updateWorkflowNotificationSettings;
2506
+ if (!saved) throw new Error("The notification settings were not saved.");
2507
+ return saved;
2508
+ }
2509
+ function useWorkflowNotificationSettings(workflowKey) {
2510
+ const [loaded, setLoaded] = useState(null);
2511
+ useEffect(() => {
2512
+ if (!workflowKey) return;
2513
+ let cancelled = false;
2514
+ fetchWorkflowNotificationSettings(workflowKey).then((settings) => {
2515
+ if (!cancelled) setLoaded({
2516
+ workflowKey,
2517
+ settings,
2518
+ error: null
2519
+ });
2520
+ }, (err) => {
2521
+ if (!cancelled) setLoaded({
2522
+ workflowKey,
2523
+ settings: null,
2524
+ error: err instanceof Error ? err : new Error(String(err))
2525
+ });
2526
+ });
2527
+ return () => {
2528
+ cancelled = true;
2529
+ };
2530
+ }, [workflowKey]);
2531
+ const save = useCallback(async (changes) => {
2532
+ if (!workflowKey) throw new Error("No workflow selected.");
2533
+ const saved = await saveWorkflowNotificationSettings(workflowKey, changes);
2534
+ setLoaded({
2535
+ workflowKey,
2536
+ settings: saved,
2537
+ error: null
2538
+ });
2539
+ return saved;
2540
+ }, [workflowKey]);
2541
+ const current = loaded?.workflowKey === workflowKey ? loaded : null;
2542
+ return {
2543
+ settings: current?.settings ?? null,
2544
+ loading: !!workflowKey && current === null,
2545
+ error: current?.error ?? null,
2546
+ save
2547
+ };
2548
+ }
2549
+
2550
+ // src/modules/settings/components/WorkflowNotificationsTab.tsx
2551
+ /**
2552
+ * Settings → Notifications (BOFF-9974): what this workflow's person hears
2553
+ * about, through the platform's notifications: the bell in the app, and a
2554
+ * push to their registered devices unless they switch that off.
2555
+ *
2556
+ * The events are a PRODUCTION run failing or succeeding, an AI Agent waiting
2557
+ * for a tool-call approval, and a trigger switched off after repeated errors.
2558
+ * Run outcomes are throttled per workflow: at most one notification of each
2559
+ * kind per window, the next one says how many were held back. The person is
2560
+ * whoever the run ran as (who started it, or the owner of its trigger), else
2561
+ * the workflow's creator.
2562
+ */
2563
+ function formOf(settings) {
2564
+ return {
2565
+ notifyOnFailure: settings.notifyOnFailure,
2566
+ notifyOnSuccess: settings.notifyOnSuccess,
2567
+ notifyOnApprovalRequest: settings.notifyOnApprovalRequest,
2568
+ notifyOnTriggerDisabled: settings.notifyOnTriggerDisabled,
2569
+ pushEnabled: settings.pushEnabled,
2570
+ throttleMinutes: String(settings.throttleMinutes)
2571
+ };
2572
+ }
2573
+ function WorkflowNotificationsTab({ workflowKey }) {
2574
+ const tr = useTr();
2575
+ const { settings, loading, error, save } = useWorkflowNotificationSettings(workflowKey);
2576
+ const [form, setForm] = useState(null);
2577
+ const [saving, setSaving] = useState(false);
2578
+ const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
2579
+ const locked = loading || !form || saving || !canEdit;
2580
+ useEffect(() => {
2581
+ if (!settings) return;
2582
+ setForm(formOf(settings));
2583
+ }, [settings]);
2584
+ if (!workflowKey) return /* @__PURE__ */ jsxs("div", {
2585
+ className: "flex items-center gap-2 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-3",
2586
+ "data-testid": "workflow-notifications-no-workflow",
2587
+ children: [/* @__PURE__ */ jsx(Icon, {
2588
+ icon: "mdi:information",
2589
+ width: 18,
2590
+ className: "shrink-0 text-status-warning-text"
2591
+ }), /* @__PURE__ */ jsx("p", {
2592
+ className: "text-sm text-status-warning-text",
2593
+ children: tr("fluidgrids.settings.workflowNotifications.noWorkflow", "No workflow selected. Open Settings from a workflow to choose what it notifies you about.")
2594
+ })]
2595
+ });
2596
+ if (error) return /* @__PURE__ */ jsx("p", {
2597
+ className: "text-sm text-status-error-text",
2598
+ role: "alert",
2599
+ children: tr("fluidgrids.settings.workflowNotifications.loadFailed", "Could not load this workflow's notification settings.")
2600
+ });
2601
+ const handleSave = async () => {
2602
+ if (!form) return;
2603
+ const throttleMinutes = parseBoundedInt(form.throttleMinutes, NOTIFICATION_THROTTLE_RANGE);
2604
+ if (throttleMinutes === void 0) {
2605
+ toast.error(tr("fluidgrids.settings.workflowNotifications.throttleRequired", "Enter a number of minutes from 0 to 1440."));
2606
+ return;
2607
+ }
2608
+ setSaving(true);
2609
+ try {
2610
+ await save({
2611
+ notifyOnFailure: form.notifyOnFailure,
2612
+ notifyOnSuccess: form.notifyOnSuccess,
2613
+ notifyOnApprovalRequest: form.notifyOnApprovalRequest,
2614
+ notifyOnTriggerDisabled: form.notifyOnTriggerDisabled,
2615
+ pushEnabled: form.pushEnabled,
2616
+ throttleMinutes
2617
+ });
2618
+ toast.success(tr("fluidgrids.settings.workflowNotifications.savedToast", "Notification settings saved"));
2619
+ } catch (err) {
2620
+ toast.error(tr("fluidgrids.settings.runData.saveFailedToast", "Failed to save settings"), { description: err instanceof Error ? err.message : void 0 });
2621
+ } finally {
2622
+ setSaving(false);
2623
+ }
2624
+ };
2625
+ const flagSwitch = (key, label) => /* @__PURE__ */ jsx(Switch, {
2626
+ "aria-label": label,
2627
+ checked: form?.[key] ?? false,
2628
+ onCheckedChange: (checked) => setForm((f) => f ? {
2629
+ ...f,
2630
+ [key]: checked
2631
+ } : f),
2632
+ disabled: locked,
2633
+ "data-testid": `workflow-notifications-${key}`
2634
+ });
2635
+ const failureLabel = tr("fluidgrids.settings.workflowNotifications.onFailureLabel", "A run fails");
2636
+ const successLabel = tr("fluidgrids.settings.workflowNotifications.onSuccessLabel", "A run succeeds");
2637
+ const approvalLabel = tr("fluidgrids.settings.workflowNotifications.onApprovalLabel", "An AI Agent waits for approval");
2638
+ const triggerLabel = tr("fluidgrids.settings.workflowNotifications.onTriggerDisabledLabel", "A trigger is switched off");
2639
+ const inAppLabel = tr("fluidgrids.settings.workflowNotifications.inAppLabel", "In the app");
2640
+ const pushLabel = tr("fluidgrids.settings.workflowNotifications.pushLabel", "Push to your devices");
2641
+ const throttleLabel = tr("fluidgrids.settings.workflowNotifications.throttleLabel", "At most one notification per");
2642
+ return /* @__PURE__ */ jsxs("div", {
2643
+ "data-testid": "workflow-notifications-settings",
2644
+ children: [
2645
+ /* @__PURE__ */ jsxs("div", {
2646
+ className: "mb-6 flex items-start gap-2 rounded-lg border border-border-secondary bg-background-secondary p-3",
2647
+ children: [/* @__PURE__ */ jsx(Icon, {
2648
+ icon: "mdi:bell-outline",
2649
+ width: 18,
2650
+ className: "mt-0.5 shrink-0 text-text-secondary"
2651
+ }), /* @__PURE__ */ jsx("p", {
2652
+ className: "text-sm text-text-secondary",
2653
+ children: tr("fluidgrids.settings.workflowNotifications.intro", "Notifications go to the person a run ran as (whoever started it, or the owner of its trigger), else to the workflow's creator. Run outcomes notify only for production runs; runs you test from the editor never do.")
2654
+ })]
2655
+ }),
2656
+ !canEdit && /* @__PURE__ */ jsx("p", {
2657
+ className: "mb-4 text-xs text-text-secondary",
2658
+ role: "note",
2659
+ children: tr("fluidgrids.settings.execution.readOnly", "You can see these settings but not change them: changing them needs permission to edit workflows.")
2660
+ }),
2661
+ /* @__PURE__ */ jsxs("h3", {
2662
+ className: "mb-1 flex items-center gap-2 text-sm font-semibold text-text-primary",
2663
+ children: [/* @__PURE__ */ jsx(Icon, {
2664
+ icon: "mdi:bell-ring-outline",
2665
+ width: 18
2666
+ }), tr("fluidgrids.settings.workflowNotifications.eventsTitle", "Notify when")]
2667
+ }),
2668
+ /* @__PURE__ */ jsxs("div", {
2669
+ className: "mb-6 divide-y divide-border-secondary",
2670
+ children: [
2671
+ /* @__PURE__ */ jsx(SettingRow, {
2672
+ label: failureLabel,
2673
+ description: tr("fluidgrids.settings.workflowNotifications.onFailureDescription", "A production run of this workflow ends with an error."),
2674
+ children: flagSwitch("notifyOnFailure", failureLabel)
2675
+ }),
2676
+ /* @__PURE__ */ jsx(SettingRow, {
2677
+ label: successLabel,
2678
+ description: tr("fluidgrids.settings.workflowNotifications.onSuccessDescription", "A production run of this workflow completes."),
2679
+ children: flagSwitch("notifyOnSuccess", successLabel)
2680
+ }),
2681
+ /* @__PURE__ */ jsx(SettingRow, {
2682
+ label: approvalLabel,
2683
+ description: tr("fluidgrids.settings.workflowNotifications.onApprovalDescription", "An AI Agent node needs someone to approve a tool call before the run can go on."),
2684
+ children: flagSwitch("notifyOnApprovalRequest", approvalLabel)
2685
+ }),
2686
+ /* @__PURE__ */ jsx(SettingRow, {
2687
+ label: triggerLabel,
2688
+ description: tr("fluidgrids.settings.workflowNotifications.onTriggerDisabledDescription", "A polling trigger stopped itself after failing several times in a row."),
2689
+ children: flagSwitch("notifyOnTriggerDisabled", triggerLabel)
2690
+ })
2691
+ ]
2692
+ }),
2693
+ /* @__PURE__ */ jsxs("h3", {
2694
+ className: "mb-1 flex items-center gap-2 text-sm font-semibold text-text-primary",
2695
+ children: [/* @__PURE__ */ jsx(Icon, {
2696
+ icon: "mdi:send-outline",
2697
+ width: 18
2698
+ }), tr("fluidgrids.settings.workflowNotifications.channelsTitle", "Channels")]
2699
+ }),
2700
+ /* @__PURE__ */ jsxs("div", {
2701
+ className: "mb-6 divide-y divide-border-secondary",
2702
+ children: [
2703
+ /* @__PURE__ */ jsx(SettingRow, {
2704
+ label: inAppLabel,
2705
+ description: tr("fluidgrids.settings.workflowNotifications.inAppDescription", "Every notification appears in the bell. This is always on."),
2706
+ children: /* @__PURE__ */ jsx(Switch, {
2707
+ "aria-label": inAppLabel,
2708
+ checked: true,
2709
+ disabled: true
2710
+ })
2711
+ }),
2712
+ /* @__PURE__ */ jsx(SettingRow, {
2713
+ label: pushLabel,
2714
+ description: tr("fluidgrids.settings.workflowNotifications.pushDescription", "Also send each notification to the phones and browsers where you allowed notifications."),
2715
+ children: flagSwitch("pushEnabled", pushLabel)
2716
+ }),
2717
+ /* @__PURE__ */ jsx(SettingRow, {
2718
+ label: throttleLabel,
2719
+ description: tr("fluidgrids.settings.workflowNotifications.throttleDescription", "Failures or successes inside this window are counted and summed up in the next notification. 0 sends every one."),
2720
+ children: /* @__PURE__ */ jsxs("div", {
2721
+ className: "flex items-center gap-2",
2722
+ children: [/* @__PURE__ */ jsx(Input, {
2723
+ type: "number",
2724
+ min: NOTIFICATION_THROTTLE_RANGE.min,
2725
+ max: NOTIFICATION_THROTTLE_RANGE.max,
2726
+ "aria-label": throttleLabel,
2727
+ value: form?.throttleMinutes ?? "",
2728
+ onChange: (e) => {
2729
+ const throttleMinutes = e.target.value;
2730
+ setForm((f) => f ? {
2731
+ ...f,
2732
+ throttleMinutes
2733
+ } : f);
2734
+ },
2735
+ disabled: locked,
2736
+ className: "w-[100px] text-right",
2737
+ "data-testid": "workflow-notifications-throttleMinutes"
2738
+ }), /* @__PURE__ */ jsx("span", {
2739
+ className: "text-xs text-text-secondary",
2740
+ children: tr("fluidgrids.settings.workflowNotifications.minutesUnit", "minutes")
2741
+ })]
2742
+ })
2743
+ })
2744
+ ]
2745
+ }),
2746
+ /* @__PURE__ */ jsxs("div", {
2747
+ className: "flex flex-wrap items-center justify-between gap-3",
2748
+ children: [/* @__PURE__ */ jsx("p", {
2749
+ className: "text-xs text-text-secondary",
2750
+ children: settings && !settings.customized ? tr("fluidgrids.settings.workflowNotifications.defaultsNote", "These are the defaults; nobody has changed them for this workflow yet.") : null
2751
+ }), /* @__PURE__ */ jsx(Button$1, {
2752
+ "data-agent": "confirm",
2753
+ onClick: () => void handleSave(),
2754
+ disabled: locked,
2755
+ "data-testid": "workflow-notifications-save",
2756
+ children: saving ? tr("fluidgrids.settings.execution.saving", "Saving…") : tr("fluidgrids.settings.execution.save", "Save")
2757
+ })]
2758
+ })
2759
+ ]
2760
+ });
2761
+ }
2762
+
2473
2763
  // src/modules/settings/components/RunControlTab.tsx
2474
2764
  /**
2475
2765
  * Settings → Run control (BOFF-9906, Phase C): the workspace's activity pause
@@ -4928,12 +5218,6 @@ const DEFAULT_SETTINGS = {
4928
5218
  retryDelaySeconds: 10,
4929
5219
  concurrentExecutionLimit: 5
4930
5220
  },
4931
- notifications: {
4932
- emailOnSuccess: false,
4933
- emailOnFailure: true,
4934
- webhookOnSuccess: false,
4935
- webhookOnFailure: true
4936
- },
4937
5221
  api: {
4938
5222
  gatewayUrl: "/api/v1/workflows",
4939
5223
  webhookUrlFormat: "/api/v1/webhooks/workflow/{workflowKey}/trigger"
@@ -5402,64 +5686,6 @@ function ExecutionTab({ workflowKey }) {
5402
5686
  })
5403
5687
  ] });
5404
5688
  }
5405
- function NotificationsTab() {
5406
- const tr = useTr();
5407
- const settings = DEFAULT_SETTINGS.notifications;
5408
- return /* @__PURE__ */ jsxs("div", { children: [
5409
- /* @__PURE__ */ jsx(ReadOnlyBanner, {}),
5410
- /* @__PURE__ */ jsxs("div", {
5411
- className: "mb-6",
5412
- children: [/* @__PURE__ */ jsxs("h3", {
5413
- className: "text-sm font-semibold text-text-primary flex items-center gap-2 mb-3",
5414
- children: [/* @__PURE__ */ jsx(Icon, {
5415
- icon: "mdi:email-outline",
5416
- width: 18
5417
- }), tr("fluidgrids.settings.notifications.emailSectionTitle", "Email Notifications")]
5418
- }), /* @__PURE__ */ jsxs("div", {
5419
- className: "divide-y divide-border-secondary rounded-lg border border-border-secondary bg-background-secondary p-1",
5420
- children: [/* @__PURE__ */ jsx(SettingRow, {
5421
- label: tr("fluidgrids.settings.notifications.emailOnSuccessLabel", "On Success"),
5422
- description: tr("fluidgrids.settings.notifications.emailOnSuccessDescription", "Send email when a workflow execution completes successfully"),
5423
- children: /* @__PURE__ */ jsx(Switch, {
5424
- checked: settings.emailOnSuccess,
5425
- disabled: true
5426
- })
5427
- }), /* @__PURE__ */ jsx(SettingRow, {
5428
- label: tr("fluidgrids.settings.notifications.emailOnFailureLabel", "On Failure"),
5429
- description: tr("fluidgrids.settings.notifications.emailOnFailureDescription", "Send email when a workflow execution fails"),
5430
- children: /* @__PURE__ */ jsx(Switch, {
5431
- checked: settings.emailOnFailure,
5432
- disabled: true
5433
- })
5434
- })]
5435
- })]
5436
- }),
5437
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h3", {
5438
- className: "text-sm font-semibold text-text-primary flex items-center gap-2 mb-3",
5439
- children: [/* @__PURE__ */ jsx(Icon, {
5440
- icon: "mdi:webhook",
5441
- width: 18
5442
- }), tr("fluidgrids.settings.notifications.webhookSectionTitle", "Webhook Notifications")]
5443
- }), /* @__PURE__ */ jsxs("div", {
5444
- className: "divide-y divide-border-secondary rounded-lg border border-border-secondary bg-background-secondary p-1",
5445
- children: [/* @__PURE__ */ jsx(SettingRow, {
5446
- label: tr("fluidgrids.settings.notifications.webhookOnSuccessLabel", "On Success"),
5447
- description: tr("fluidgrids.settings.notifications.webhookOnSuccessDescription", "Send webhook when a workflow execution completes successfully"),
5448
- children: /* @__PURE__ */ jsx(Switch, {
5449
- checked: settings.webhookOnSuccess,
5450
- disabled: true
5451
- })
5452
- }), /* @__PURE__ */ jsx(SettingRow, {
5453
- label: tr("fluidgrids.settings.notifications.webhookOnFailureLabel", "On Failure"),
5454
- description: tr("fluidgrids.settings.notifications.webhookOnFailureDescription", "Send webhook when a workflow execution fails"),
5455
- children: /* @__PURE__ */ jsx(Switch, {
5456
- checked: settings.webhookOnFailure,
5457
- disabled: true
5458
- })
5459
- })]
5460
- })] })
5461
- ] });
5462
- }
5463
5689
  function ApiTab() {
5464
5690
  const tr = useTr();
5465
5691
  const settings = DEFAULT_SETTINGS.api;
@@ -5743,7 +5969,7 @@ function SettingsPage({ workflowKey: workflowKeyProp } = {}) {
5743
5969
  }),
5744
5970
  /* @__PURE__ */ jsx(TabsContent, {
5745
5971
  value: "notifications",
5746
- children: /* @__PURE__ */ jsx(NotificationsTab, {})
5972
+ children: /* @__PURE__ */ jsx(WorkflowNotificationsTab, { workflowKey })
5747
5973
  }),
5748
5974
  logStreamsEnabled ? /* @__PURE__ */ jsx(TabsContent, {
5749
5975
  value: "log-streaming",
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { A as DialogTitle, D as DialogHeader, E as DialogFooter, S as Dialog, T as DialogDescription, b as Badge, w as DialogContent } from "./flow-builder-CmJi6XWS.js";
3
- import { i as Button } from "./PublishCustomNodeAction-CtRHgitG.js";
2
+ import { A as DialogTitle, D as DialogHeader, E as DialogFooter, S as Dialog, T as DialogDescription, b as Badge, w as DialogContent } from "./flow-builder-28Hp8NgO.js";
3
+ import { i as Button } from "./PublishCustomNodeAction-ByxKmyOF.js";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { AlertTriangle, CheckCircle2, Download, ExternalLink, Loader2, Puzzle, ShoppingCart } from "lucide-react";
6
6