@burdenoff/fluidgrids-fe-sdk 2026.1108.1 → 2026.1108.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{ExecutionDetailPage-STW6RPK6.js → ExecutionDetailPage-PY5OE2RJ.js} +15 -14
  2. package/dist/{ExecutionsListPage-753XDOTM.js → ExecutionsListPage-LKNYPXNZ.js} +11 -10
  3. package/dist/{TemplateDetailPanel-ZTYXKWRI.js → TemplateDetailPanel-5WY75DVB.js} +1 -1
  4. package/dist/{WorkflowBuilder-HY3PZZVD.js → WorkflowBuilder-YFPBMOWZ.js} +16 -16
  5. package/dist/{WorkflowsList-YFDT7ZPX.js → WorkflowsList-TETNEMYP.js} +10 -10
  6. package/dist/{agentTools-3UQNOO7V.js → agentTools-BW2XMHRV.js} +6 -6
  7. package/dist/{chunk-KE677WYY.js → chunk-2CXUTQUH.js} +1 -1
  8. package/dist/{chunk-H4ERTSMX.js → chunk-2VSYOGNI.js} +1948 -35
  9. package/dist/{chunk-NZMUKAXG.js → chunk-54NXUOHI.js} +1 -1
  10. package/dist/{chunk-YQOXXM5V.js → chunk-62RJORF7.js} +4 -4
  11. package/dist/{chunk-26GYGAC5.js → chunk-66AKFXP7.js} +15 -15
  12. package/dist/{chunk-47OW3OM5.js → chunk-7SSMRID2.js} +5 -5
  13. package/dist/{chunk-I23KAUR6.js → chunk-7XW6BC4B.js} +1 -1
  14. package/dist/{chunk-5375NUIO.js → chunk-AR22A74Y.js} +11 -0
  15. package/dist/{chunk-QHRRYTOL.js → chunk-D5Y3DHGJ.js} +1 -1
  16. package/dist/{chunk-JXJ2DESW.js → chunk-DG4UO5OY.js} +5 -5
  17. package/dist/{chunk-NMYENCP2.js → chunk-E7KIH3CF.js} +1 -1
  18. package/dist/{chunk-5OVWMFPL.js → chunk-GI6BQIQA.js} +124 -42
  19. package/dist/{chunk-Z3ABLERJ.js → chunk-GOZR72NK.js} +64 -1
  20. package/dist/{chunk-KHS2DZHH.js → chunk-HICQ4J4W.js} +1 -1
  21. package/dist/chunk-HPNZ3PYU.js +1448 -0
  22. package/dist/{chunk-F522K3P5.js → chunk-HQSSW7K3.js} +8 -8
  23. package/dist/{chunk-63X5ORDO.js → chunk-HSOSMPRC.js} +1 -1
  24. package/dist/{chunk-JRSCUUBT.js → chunk-IPJV2IOH.js} +1 -1
  25. package/dist/{chunk-HPYXHIRI.js → chunk-IWLNBDR2.js} +5 -5
  26. package/dist/{chunk-3QV3L3YY.js → chunk-IZOPKR64.js} +3 -3
  27. package/dist/{chunk-2CM26W2I.js → chunk-JRR4VKYD.js} +95 -6
  28. package/dist/{chunk-4OZISYDF.js → chunk-JSJSBB76.js} +2 -2
  29. package/dist/{chunk-PF7XQA2S.js → chunk-KJB3WZVD.js} +2 -2
  30. package/dist/{chunk-OHKSDHNC.js → chunk-L7PHZ7RY.js} +2 -2
  31. package/dist/{chunk-HRZMKJEC.js → chunk-LOMLLWHD.js} +705 -12
  32. package/dist/{chunk-XAXZ4T6H.js → chunk-MYPB2KTJ.js} +13 -6
  33. package/dist/{chunk-TKHW2G7O.js → chunk-N2QXEV5Y.js} +7 -7
  34. package/dist/{chunk-EXJW7EKR.js → chunk-OJHBCOBA.js} +164 -1
  35. package/dist/{chunk-ZKRDO4IG.js → chunk-POYMF7L6.js} +1 -1
  36. package/dist/{chunk-WX5BNZVX.js → chunk-RNYFPG7R.js} +4 -4
  37. package/dist/{chunk-PASKZCII.js → chunk-TN3NGTF7.js} +3 -3
  38. package/dist/{chunk-TYE252O5.js → chunk-VH3EET6H.js} +2 -2
  39. package/dist/{chunk-UZJKX5FN.js → chunk-X3F5IQBP.js} +5 -3
  40. package/dist/chunk-YASVILWQ.js +398 -0
  41. package/dist/{chunk-36TEHKVN.js → chunk-ZHXJYOOG.js} +2115 -25
  42. package/dist/executions.js +16 -15
  43. package/dist/history.js +8 -8
  44. package/dist/index.d.ts +15456 -235
  45. package/dist/index.js +1057 -55
  46. package/dist/{node-detail-view-GFC3UDYD.js → node-detail-view-BGWKE6KW.js} +489 -21
  47. package/dist/nodes.js +2 -2
  48. package/dist/observability.js +8 -8
  49. package/dist/{quick-config-panel-6XUUXIHX.js → quick-config-panel-67BU4I23.js} +8 -8
  50. package/dist/settings.js +10 -8
  51. package/dist/templates.js +15 -15
  52. package/dist/workflow-builder.js +17 -17
  53. package/dist/workflow-preview.js +2 -2
  54. package/dist/workflows-list.js +10 -10
  55. package/package.json +2 -2
  56. package/dist/chunk-ODWKSYMZ.js +0 -529
@@ -1,16 +1,17 @@
1
- import { useDurableRunFields, useRunHistory, useDeterminismStatus, useRunStackTrace, RunHistoryView, SignalRunDialog, terminateDurableRun } from './chunk-XAXZ4T6H.js';
2
- import { useDurableParityRunFields, hasAgentNodeState, agentNodeTraces, orderNodesByExecution, useRunMetadata, useRunPending, ExecutionStatusBadge, NodeTimeline, RunLogViewer, NodeStatusBadge, setRunVersioningOverride } from './chunk-WX5BNZVX.js';
3
- import { useExecution, useExecutionActions, useAutoRefresh, runChainLinks, shortRunId, callbackTone, parseMetadataDefinition, overrideRequestOf } from './chunk-2CM26W2I.js';
4
- import { fetchRunFailureClusters, OPEN_STATUSES, dismissRunFailureCluster, BUCKET_LABELS } from './chunk-NZMUKAXG.js';
1
+ import { useDurableRunFields, useRunHistory, useDeterminismStatus, useRunStackTrace, RunHistoryView, SignalRunDialog, terminateDurableRun } from './chunk-MYPB2KTJ.js';
2
+ import { useDurableParityRunFields, hasAgentNodeState, agentNodeTraces, orderNodesByExecution, useRunPending, useRunMetadata, ExecutionStatusBadge, NodeTimeline, RunLogViewer, NodeStatusBadge, setRunVersioningOverride } from './chunk-RNYFPG7R.js';
3
+ import { useExecution, useExecutionActions, useAutoRefresh, runChainLinks, shortRunId, callbackTone, parseMetadataDefinition, overrideRequestOf } from './chunk-JRR4VKYD.js';
4
+ import { fetchRunFailureClusters, OPEN_STATUSES, dismissRunFailureCluster, BUCKET_LABELS } from './chunk-54NXUOHI.js';
5
5
  import { aiCost, formatTokens, modelLabel } from './chunk-NZAYYUQ4.js';
6
- import { useRunPolicyDecisions, RunPolicyDecisions } from './chunk-EXJW7EKR.js';
7
- import { EntityTagPicker, canSaveRunAsTestCase, useWorkflowProposal, WorkflowProposalCard, MAX_CASE_NAME, saveRunAsTestCase } from './chunk-PASKZCII.js';
8
- import { buildNodeDisplayMap, formatRunVersion, nodeDisplayTitle } from './chunk-KHS2DZHH.js';
6
+ import { useRunPolicyDecisions, RunPolicyDecisions, checkCodecServerUrl, containsEncodedPayload, isEncodedPayload, encodedPayloadBytes, decodePayloads, CodecServerError } from './chunk-OJHBCOBA.js';
7
+ import { readTyped, writeTyped, useResource, fetchCodecServerSettings, fetchRunControlFields, fetchPauseRules, RUN_CONTROL_DIALOG_STYLE, fetchRunHistoryArchive, parseJsonInput, NativeSelect, JsonField, FieldError, StatusPill, JsonBlock, fetchSignalTokens, parseJsonObjectInput, queryRun, revokeSignalToken, upsertRunSearchAttributes, newRunControlId, fetchRunUpdate, createSignalToken, pauseActivities, unpauseActivities, resetActivities, updateActivityOptions, completeActivityById, failActivityById, sendRunUpdate, resetRunToLastCleanTask, resetRun } from './chunk-YASVILWQ.js';
8
+ import { EntityTagPicker, canSaveRunAsTestCase, useWorkflowProposal, WorkflowProposalCard, MAX_CASE_NAME, saveRunAsTestCase } from './chunk-TN3NGTF7.js';
9
+ import { buildNodeDisplayMap, formatRunVersion, nodeDisplayTitle } from './chunk-HICQ4J4W.js';
9
10
  import { AiCreditBalanceSlot } from './chunk-Y53CVZVV.js';
10
- import { useWorkflowVersions } from './chunk-PF7XQA2S.js';
11
- import { useDurableFunctionNodesEnabled, useDurableParityEnabled, runSignalTargets, canSignalRun, formatElapsedMs, runDurationMs, useWorkflowTestsEnabled } from './chunk-5OVWMFPL.js';
12
- import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-5375NUIO.js';
13
- import { RunLogsDocument, RunLogAppendedDocument, ExplainRunDocument, RunAiUsageDocument } from './chunk-Z3ABLERJ.js';
11
+ import { useWorkflowVersions } from './chunk-KJB3WZVD.js';
12
+ import { useDurableFunctionNodesEnabled, useDurableParityEnabled, usePhaseCFlags, runSignalTargets, canSignalRun, formatElapsedMs, runDurationMs, useWorkflowTestsEnabled, durableInterfaceOf } from './chunk-GI6BQIQA.js';
13
+ import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-AR22A74Y.js';
14
+ import { TaskQueuesDocument, TaskQueueStatsDocument, CreateTaskQueueDocument, UpdateTaskQueueDocument, DeleteTaskQueueDocument, WorkflowWorkersDocument, WorkerTokensDocument, CreateWorkerTokenDocument, RotateWorkerTokenDocument, DeleteWorkerTokenDocument, SetWorkerTokenConnectionAllowlistDocument, RunLogsDocument, RunLogAppendedDocument, ExplainRunDocument, RunAiUsageDocument } from './chunk-GOZR72NK.js';
14
15
  import { useTr } from './chunk-UULVIT25.js';
15
16
  import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-PDODDDHY.js';
16
17
  import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
@@ -20,7 +21,7 @@ import { useParams, useNavigate, useSearchParams, Link } from 'react-router';
20
21
  import { Icon } from '@iconify/react';
21
22
  import { toast } from 'sonner';
22
23
  import { Button } from '@burdenoff/fe-libs/button';
23
- import { Card, CardHeader, Badge, CardContent, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Input, Button as Button$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@burdenoff/fe-libs/ui';
24
+ import { Card, CardHeader, Badge, CardContent, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Input, Button as Button$1, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DropdownMenuSeparator, Switch } from '@burdenoff/fe-libs/ui';
24
25
  import { useActorProfiles, buildActorFallback } from '@burdenoff/fe-libs/shared';
25
26
  import { gql } from '@apollo/client';
26
27
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -463,7 +464,7 @@ function HumanRequestCard({
463
464
  }) {
464
465
  const tr = useTr();
465
466
  const { respond, loading } = useRespondToHumanRequest();
466
- const [text, setText] = useState("");
467
+ const [text2, setText] = useState("");
467
468
  const [values, setValues] = useState({});
468
469
  const submit = async (answer) => {
469
470
  try {
@@ -490,7 +491,7 @@ function HumanRequestCard({
490
491
  const missingRequired = request.responseType === "CUSTOM_FORM" && request.fields.some(
491
492
  (field) => field.required && !(values[field.name] ?? "").trim()
492
493
  );
493
- const emptyText = request.responseType === "FREE_TEXT" && !text.trim();
494
+ const emptyText = request.responseType === "FREE_TEXT" && !text2.trim();
494
495
  const recipient = useRecipientLabel(
495
496
  request,
496
497
  request.channel === "IN_APP" ? tr("fluidgrids.humanResponse.anyMember", "any workspace member") : ""
@@ -547,7 +548,7 @@ function HumanRequestCard({
547
548
  Textarea,
548
549
  {
549
550
  id: `human-text-${request.id}`,
550
- value: text,
551
+ value: text2,
551
552
  onChange: (event) => setText(event.target.value)
552
553
  }
553
554
  )
@@ -598,7 +599,7 @@ function HumanRequestCard({
598
599
  size: "sm",
599
600
  disabled: loading || missingRequired || emptyText,
600
601
  onClick: () => submit(
601
- request.responseType === "FREE_TEXT" ? { text } : { values }
602
+ request.responseType === "FREE_TEXT" ? { text: text2 } : { values }
602
603
  ),
603
604
  "data-testid": "human-request-submit",
604
605
  children: [
@@ -1292,7 +1293,7 @@ function urlTransform(url, key) {
1292
1293
  return safeMarkdownHref(url);
1293
1294
  }
1294
1295
  function RunMarkdown({
1295
- text,
1296
+ text: text2,
1296
1297
  "data-testid": testId
1297
1298
  }) {
1298
1299
  return /* @__PURE__ */ jsx(
@@ -1306,7 +1307,7 @@ function RunMarkdown({
1306
1307
  components: COMPONENTS,
1307
1308
  skipHtml: true,
1308
1309
  urlTransform,
1309
- children: text
1310
+ children: text2
1310
1311
  }
1311
1312
  )
1312
1313
  }
@@ -1981,6 +1982,2042 @@ function useRunAiUsage(runId, { enabled, live }) {
1981
1982
  refetch: read
1982
1983
  };
1983
1984
  }
1985
+
1986
+ // src/modules/task-queues/api.ts
1987
+ async function fetchTaskQueues() {
1988
+ return (await readTyped(TaskQueuesDocument, {})).taskQueues;
1989
+ }
1990
+ async function fetchTaskQueueStats(name, environment) {
1991
+ return (await readTyped(TaskQueueStatsDocument, {
1992
+ name,
1993
+ environment: environment ?? null
1994
+ })).taskQueueStats;
1995
+ }
1996
+ async function createTaskQueue(input) {
1997
+ return (await writeTyped(CreateTaskQueueDocument, { input })).createTaskQueue;
1998
+ }
1999
+ async function updateTaskQueue(id, input) {
2000
+ return (await writeTyped(UpdateTaskQueueDocument, { id, input })).updateTaskQueue;
2001
+ }
2002
+ async function deleteTaskQueue(id) {
2003
+ return (await writeTyped(DeleteTaskQueueDocument, { id })).deleteTaskQueue;
2004
+ }
2005
+ async function fetchWorkflowWorkers(filter) {
2006
+ return (await readTyped(WorkflowWorkersDocument, { filter: filter ?? null })).workflowWorkers;
2007
+ }
2008
+ async function fetchWorkerTokens() {
2009
+ return (await readTyped(WorkerTokensDocument, {})).workerTokens;
2010
+ }
2011
+ async function createWorkerToken(input) {
2012
+ return (await writeTyped(CreateWorkerTokenDocument, input)).createWorkerToken;
2013
+ }
2014
+ async function rotateWorkerToken(id) {
2015
+ return (await writeTyped(RotateWorkerTokenDocument, { id })).rotateWorkerToken;
2016
+ }
2017
+ async function deleteWorkerToken(id) {
2018
+ return (await writeTyped(DeleteWorkerTokenDocument, { id })).deleteWorkerToken;
2019
+ }
2020
+ async function setWorkerTokenConnectionAllowlist(id, names) {
2021
+ return (await writeTyped(SetWorkerTokenConnectionAllowlistDocument, { id, names })).setWorkerTokenConnectionAllowlist;
2022
+ }
2023
+
2024
+ // src/modules/run-control/utils/activity-pause.ts
2025
+ function text(value) {
2026
+ return typeof value === "string" && value.length > 0 ? value : null;
2027
+ }
2028
+ function activityPauseStates(events) {
2029
+ const states = /* @__PURE__ */ new Map();
2030
+ const ordered = [...events].sort((a, b) => a.seq - b.seq);
2031
+ for (const event of ordered) {
2032
+ if (event.type !== "ACTIVITY_PAUSED" && event.type !== "ACTIVITY_UNPAUSED")
2033
+ continue;
2034
+ const activityId = text(event.payload?.["activityId"]);
2035
+ if (!activityId) continue;
2036
+ if (event.type === "ACTIVITY_PAUSED") {
2037
+ states.set(activityId, {
2038
+ paused: true,
2039
+ reason: text(event.payload?.["reason"]),
2040
+ ruleId: text(event.payload?.["ruleId"]),
2041
+ seq: event.seq
2042
+ });
2043
+ } else {
2044
+ states.set(activityId, {
2045
+ paused: false,
2046
+ reason: null,
2047
+ ruleId: null,
2048
+ seq: event.seq
2049
+ });
2050
+ }
2051
+ }
2052
+ return states;
2053
+ }
2054
+
2055
+ // src/modules/run-control/utils/handlers.ts
2056
+ var GRAPH_RUN_QUERIES = [
2057
+ "status",
2058
+ "progress",
2059
+ "nodeStates",
2060
+ "nodeOutput"
2061
+ ];
2062
+ function isRecord2(value) {
2063
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2064
+ }
2065
+ function declaredHandlers(definitionJson, workflowFn, kind) {
2066
+ const iface = durableInterfaceOf(definitionJson, workflowFn);
2067
+ if (!iface) return { names: [], openNames: false };
2068
+ const declared = iface[kind];
2069
+ const names = isRecord2(declared) ? Object.keys(declared).sort() : Array.isArray(declared) ? declared.filter((item) => typeof item === "string").sort() : [];
2070
+ const open = isRecord2(iface.open) && iface.open[kind] === true;
2071
+ return { names, openNames: open };
2072
+ }
2073
+ function isFinalUpdateStatus(status) {
2074
+ return status === "COMPLETED" || status === "REJECTED";
2075
+ }
2076
+ function updateReached(status, waitFor) {
2077
+ if (isFinalUpdateStatus(status)) return true;
2078
+ return waitFor === "ACCEPTED" && status === "ACCEPTED";
2079
+ }
2080
+ function RunActivitiesCard({
2081
+ runId,
2082
+ activities,
2083
+ pauseStates,
2084
+ pauseRules,
2085
+ canControl,
2086
+ onChanged
2087
+ }) {
2088
+ const tr = useTr();
2089
+ const [action, setAction] = useState(null);
2090
+ const rules = useMemo(
2091
+ () => new Map(pauseRules.map((rule) => [rule.id, rule])),
2092
+ [pauseRules]
2093
+ );
2094
+ if (activities.length === 0) return null;
2095
+ return /* @__PURE__ */ jsxs(
2096
+ "section",
2097
+ {
2098
+ className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary",
2099
+ "data-testid": "run-activities-card",
2100
+ children: [
2101
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-center gap-2 border-b border-border-secondary px-4 py-3", children: [
2102
+ /* @__PURE__ */ jsx(
2103
+ Icon,
2104
+ {
2105
+ icon: "mdi:cog-play-outline",
2106
+ width: 18,
2107
+ className: "text-text-secondary"
2108
+ }
2109
+ ),
2110
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.activities.title", "Activities") }),
2111
+ canControl && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex gap-2", children: [
2112
+ /* @__PURE__ */ jsxs(
2113
+ Button,
2114
+ {
2115
+ variant: "outline",
2116
+ size: "sm",
2117
+ onClick: () => setAction({ kind: "pause", activity: null }),
2118
+ "data-testid": "run-activities-pause-all",
2119
+ children: [
2120
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:pause", className: "mr-1", width: 14 }),
2121
+ tr("fluidgrids.runControl.activities.pauseAll", "Pause all")
2122
+ ]
2123
+ }
2124
+ ),
2125
+ /* @__PURE__ */ jsxs(
2126
+ Button,
2127
+ {
2128
+ variant: "outline",
2129
+ size: "sm",
2130
+ onClick: () => setAction({ kind: "unpause", activity: null }),
2131
+ "data-testid": "run-activities-unpause-all",
2132
+ children: [
2133
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 14 }),
2134
+ tr("fluidgrids.runControl.activities.unpauseAll", "Unpause all")
2135
+ ]
2136
+ }
2137
+ )
2138
+ ] })
2139
+ ] }),
2140
+ /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border-secondary", children: activities.map((activity) => {
2141
+ const pause = pauseStates.get(activity.activityId);
2142
+ const rule = pause?.ruleId ? rules.get(pause.ruleId) : void 0;
2143
+ return /* @__PURE__ */ jsxs(
2144
+ "li",
2145
+ {
2146
+ className: "flex flex-wrap items-center gap-3 px-4 py-2.5 text-sm",
2147
+ "data-testid": `run-activity-${activity.activityId}`,
2148
+ children: [
2149
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-text-primary", children: activity.activityType }),
2150
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-text-secondary", children: [
2151
+ activity.state,
2152
+ " \xB7",
2153
+ " ",
2154
+ tr(
2155
+ "fluidgrids.runControl.activities.attempt",
2156
+ "attempt {{attempt}}",
2157
+ {
2158
+ attempt: activity.maxAttempts ? `${activity.attempt}/${activity.maxAttempts}` : activity.attempt
2159
+ }
2160
+ )
2161
+ ] }),
2162
+ pause?.paused && (pause.ruleId ? /* @__PURE__ */ jsx("span", { title: rule?.reason ?? pause.reason ?? void 0, children: /* @__PURE__ */ jsx(StatusPill, { tone: "warning", testId: "activity-paused-by-rule", children: tr(
2163
+ "fluidgrids.runControl.activities.pausedByRule",
2164
+ "Paused by rule"
2165
+ ) }) }) : /* @__PURE__ */ jsx("span", { title: pause.reason ?? void 0, children: /* @__PURE__ */ jsx(StatusPill, { tone: "warning", testId: "activity-paused", children: tr("fluidgrids.runControl.activities.paused", "Paused") }) })),
2166
+ canControl && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2167
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2168
+ Button,
2169
+ {
2170
+ variant: "ghost",
2171
+ size: "sm",
2172
+ className: "ml-auto",
2173
+ "aria-label": tr(
2174
+ "fluidgrids.runControl.activities.menu",
2175
+ "Activity actions"
2176
+ ),
2177
+ "data-testid": `run-activity-menu-${activity.activityId}`,
2178
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:dots-vertical", width: 16 })
2179
+ }
2180
+ ) }),
2181
+ /* @__PURE__ */ jsxs(
2182
+ DropdownMenuContent,
2183
+ {
2184
+ align: "end",
2185
+ style: RUN_CONTROL_DIALOG_STYLE,
2186
+ children: [
2187
+ pause?.paused ? /* @__PURE__ */ jsx(
2188
+ DropdownMenuItem,
2189
+ {
2190
+ onSelect: () => setAction({ kind: "unpause", activity }),
2191
+ children: tr(
2192
+ "fluidgrids.runControl.activities.unpause",
2193
+ "Unpause"
2194
+ )
2195
+ }
2196
+ ) : /* @__PURE__ */ jsx(
2197
+ DropdownMenuItem,
2198
+ {
2199
+ onSelect: () => setAction({ kind: "pause", activity }),
2200
+ children: tr("fluidgrids.runControl.activities.pause", "Pause")
2201
+ }
2202
+ ),
2203
+ /* @__PURE__ */ jsx(
2204
+ DropdownMenuItem,
2205
+ {
2206
+ onSelect: () => setAction({ kind: "reset", activity }),
2207
+ children: tr(
2208
+ "fluidgrids.runControl.activities.reset",
2209
+ "Reset attempts"
2210
+ )
2211
+ }
2212
+ ),
2213
+ /* @__PURE__ */ jsx(
2214
+ DropdownMenuItem,
2215
+ {
2216
+ onSelect: () => setAction({ kind: "options", activity }),
2217
+ children: tr(
2218
+ "fluidgrids.runControl.activities.options",
2219
+ "Change options\u2026"
2220
+ )
2221
+ }
2222
+ ),
2223
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
2224
+ /* @__PURE__ */ jsx(
2225
+ DropdownMenuItem,
2226
+ {
2227
+ onSelect: () => setAction({ kind: "complete", activity }),
2228
+ children: tr(
2229
+ "fluidgrids.runControl.activities.complete",
2230
+ "Complete\u2026"
2231
+ )
2232
+ }
2233
+ ),
2234
+ /* @__PURE__ */ jsx(
2235
+ DropdownMenuItem,
2236
+ {
2237
+ onSelect: () => setAction({ kind: "fail", activity }),
2238
+ children: tr("fluidgrids.runControl.activities.fail", "Fail\u2026")
2239
+ }
2240
+ )
2241
+ ]
2242
+ }
2243
+ )
2244
+ ] })
2245
+ ]
2246
+ },
2247
+ activity.activityId
2248
+ );
2249
+ }) }),
2250
+ action && /* @__PURE__ */ jsx(
2251
+ ActivityActionDialog,
2252
+ {
2253
+ runId,
2254
+ action: action.kind,
2255
+ activity: action.activity,
2256
+ onClose: () => setAction(null),
2257
+ onDone: () => {
2258
+ setAction(null);
2259
+ onChanged();
2260
+ }
2261
+ }
2262
+ )
2263
+ ]
2264
+ }
2265
+ );
2266
+ }
2267
+ function optionalMs(text2) {
2268
+ const value = text2.trim();
2269
+ if (!value) return void 0;
2270
+ const seconds = Number(value);
2271
+ return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1e3) : null;
2272
+ }
2273
+ function optionalInt(text2) {
2274
+ const value = text2.trim();
2275
+ if (!value) return void 0;
2276
+ return /^\d+$/.test(value) ? Number(value) : null;
2277
+ }
2278
+ function activityOptionsFrom(form) {
2279
+ const maxRetries = optionalInt(form.maxRetries);
2280
+ const retryDelay = optionalMs(form.retryDelaySeconds);
2281
+ const startToClose = optionalMs(form.startToCloseSeconds);
2282
+ const scheduleToClose = optionalMs(form.scheduleToCloseSeconds);
2283
+ const heartbeat = optionalMs(form.heartbeatSeconds);
2284
+ if ([maxRetries, retryDelay, startToClose, scheduleToClose, heartbeat].includes(
2285
+ null
2286
+ ))
2287
+ return null;
2288
+ const options = {};
2289
+ const retry = {};
2290
+ if (maxRetries !== void 0) retry.maxRetries = maxRetries;
2291
+ if (retryDelay !== void 0) retry.retryDelay = retryDelay;
2292
+ if (Object.keys(retry).length > 0) options.retry = retry;
2293
+ if (startToClose !== void 0) options.startToCloseTimeoutMs = startToClose;
2294
+ if (scheduleToClose !== void 0)
2295
+ options.scheduleToCloseTimeoutMs = scheduleToClose;
2296
+ if (heartbeat !== void 0) options.heartbeatTimeoutMs = heartbeat;
2297
+ return Object.keys(options).length > 0 ? options : null;
2298
+ }
2299
+ function ActivityActionDialog({
2300
+ runId,
2301
+ action,
2302
+ activity,
2303
+ onClose,
2304
+ onDone
2305
+ }) {
2306
+ const tr = useTr();
2307
+ const [reason, setReason] = useState("");
2308
+ const [resetAttempts, setResetAttempts] = useState(false);
2309
+ const [resetHeartbeat, setResetHeartbeat] = useState(false);
2310
+ const [result, setResult] = useState("");
2311
+ const [failureMessage, setFailureMessage] = useState("");
2312
+ const [nonRetryable, setNonRetryable] = useState(false);
2313
+ const [options, setOptions] = useState({
2314
+ maxRetries: "",
2315
+ retryDelaySeconds: "",
2316
+ startToCloseSeconds: "",
2317
+ scheduleToCloseSeconds: "",
2318
+ heartbeatSeconds: ""
2319
+ });
2320
+ const [busy, setBusy] = useState(false);
2321
+ const [error, setError] = useState(null);
2322
+ const selector = activity ? { activityId: activity.activityId } : { all: true };
2323
+ const parsedResult = parseJsonInput(result);
2324
+ const optionsInput = action === "options" ? activityOptionsFrom(options) : null;
2325
+ const valid = action === "pause" ? reason.trim().length > 0 : action === "complete" ? parsedResult.ok : action === "fail" ? failureMessage.trim().length > 0 : action === "options" ? optionsInput !== null : true;
2326
+ const submit = async () => {
2327
+ if (!valid) return;
2328
+ setBusy(true);
2329
+ setError(null);
2330
+ try {
2331
+ if (action === "pause")
2332
+ await pauseActivities({ runId, selector, reason: reason.trim() });
2333
+ else if (action === "unpause")
2334
+ await unpauseActivities({
2335
+ runId,
2336
+ selector,
2337
+ resetAttempts,
2338
+ resetHeartbeat
2339
+ });
2340
+ else if (action === "reset")
2341
+ await resetActivities({ runId, selector, resetHeartbeat });
2342
+ else if (action === "options" && optionsInput)
2343
+ await updateActivityOptions({ runId, selector, options: optionsInput });
2344
+ else if (action === "complete" && activity && parsedResult.ok)
2345
+ await completeActivityById({
2346
+ runId,
2347
+ activityId: activity.activityId,
2348
+ result: parsedResult.value
2349
+ });
2350
+ else if (action === "fail" && activity)
2351
+ await failActivityById({
2352
+ runId,
2353
+ activityId: activity.activityId,
2354
+ failure: {
2355
+ message: failureMessage.trim(),
2356
+ errorType: "UNKNOWN" /* Unknown */,
2357
+ nonRetryable
2358
+ }
2359
+ });
2360
+ onDone();
2361
+ } catch (err) {
2362
+ setError(describeOperationError(err));
2363
+ } finally {
2364
+ setBusy(false);
2365
+ }
2366
+ };
2367
+ const titles = {
2368
+ pause: tr(
2369
+ "fluidgrids.runControl.activities.pauseTitle",
2370
+ "Pause activities"
2371
+ ),
2372
+ unpause: tr(
2373
+ "fluidgrids.runControl.activities.unpauseTitle",
2374
+ "Unpause activities"
2375
+ ),
2376
+ reset: tr(
2377
+ "fluidgrids.runControl.activities.resetTitle",
2378
+ "Reset the attempt count"
2379
+ ),
2380
+ options: tr(
2381
+ "fluidgrids.runControl.activities.optionsTitle",
2382
+ "Change activity options"
2383
+ ),
2384
+ complete: tr(
2385
+ "fluidgrids.runControl.activities.completeTitle",
2386
+ "Complete the activity"
2387
+ ),
2388
+ fail: tr("fluidgrids.runControl.activities.failTitle", "Fail the activity")
2389
+ };
2390
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
2391
+ DialogContent,
2392
+ {
2393
+ style: RUN_CONTROL_DIALOG_STYLE,
2394
+ "data-testid": "activity-action-dialog",
2395
+ children: [
2396
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2397
+ /* @__PURE__ */ jsx(DialogTitle, { children: titles[action] }),
2398
+ /* @__PURE__ */ jsx(DialogDescription, { children: activity ? activity.activityType : tr(
2399
+ "fluidgrids.runControl.activities.everyOpen",
2400
+ "Every open activity of the run"
2401
+ ) })
2402
+ ] }),
2403
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2404
+ action === "pause" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2405
+ /* @__PURE__ */ jsx(Label, { htmlFor: "activity-pause-reason", children: tr("fluidgrids.runControl.activities.reason", "Reason") }),
2406
+ /* @__PURE__ */ jsx(
2407
+ Input,
2408
+ {
2409
+ id: "activity-pause-reason",
2410
+ value: reason,
2411
+ maxLength: 1e3,
2412
+ onChange: (event) => setReason(event.target.value),
2413
+ "data-testid": "activity-pause-reason"
2414
+ }
2415
+ )
2416
+ ] }),
2417
+ action === "unpause" && /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
2418
+ tr(
2419
+ "fluidgrids.runControl.activities.resetAttemptsToggle",
2420
+ "Restart the attempt count"
2421
+ ),
2422
+ /* @__PURE__ */ jsx(
2423
+ Switch,
2424
+ {
2425
+ checked: resetAttempts,
2426
+ onCheckedChange: setResetAttempts
2427
+ }
2428
+ )
2429
+ ] }),
2430
+ (action === "unpause" || action === "reset") && /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
2431
+ tr(
2432
+ "fluidgrids.runControl.activities.resetHeartbeat",
2433
+ "Drop the heartbeat details"
2434
+ ),
2435
+ /* @__PURE__ */ jsx(
2436
+ Switch,
2437
+ {
2438
+ checked: resetHeartbeat,
2439
+ onCheckedChange: setResetHeartbeat
2440
+ }
2441
+ )
2442
+ ] }),
2443
+ action === "options" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
2444
+ [
2445
+ [
2446
+ "maxRetries",
2447
+ tr(
2448
+ "fluidgrids.runControl.activities.maxRetries",
2449
+ "Max retries"
2450
+ )
2451
+ ],
2452
+ [
2453
+ "retryDelaySeconds",
2454
+ tr(
2455
+ "fluidgrids.runControl.activities.retryDelay",
2456
+ "First retry delay (s)"
2457
+ )
2458
+ ],
2459
+ [
2460
+ "startToCloseSeconds",
2461
+ tr(
2462
+ "fluidgrids.runControl.activities.startToClose",
2463
+ "Start-to-close timeout (s)"
2464
+ )
2465
+ ],
2466
+ [
2467
+ "scheduleToCloseSeconds",
2468
+ tr(
2469
+ "fluidgrids.runControl.activities.scheduleToClose",
2470
+ "Schedule-to-close timeout (s)"
2471
+ )
2472
+ ],
2473
+ [
2474
+ "heartbeatSeconds",
2475
+ tr(
2476
+ "fluidgrids.runControl.activities.heartbeat",
2477
+ "Heartbeat timeout (s)"
2478
+ )
2479
+ ]
2480
+ ].map(([field, label]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2481
+ /* @__PURE__ */ jsx(Label, { htmlFor: `activity-option-${field}`, children: label }),
2482
+ /* @__PURE__ */ jsx(
2483
+ Input,
2484
+ {
2485
+ id: `activity-option-${field}`,
2486
+ inputMode: "decimal",
2487
+ value: options[field],
2488
+ onChange: (event) => setOptions((current) => ({
2489
+ ...current,
2490
+ [field]: event.target.value
2491
+ })),
2492
+ "data-testid": `activity-option-${field}`
2493
+ }
2494
+ )
2495
+ ] }, field)),
2496
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary sm:col-span-2", children: tr(
2497
+ "fluidgrids.runControl.activities.optionsHint",
2498
+ "Empty fields stay as they are. The change applies from the next attempt."
2499
+ ) })
2500
+ ] }),
2501
+ action === "complete" && /* @__PURE__ */ jsx(
2502
+ JsonField,
2503
+ {
2504
+ id: "activity-complete-result",
2505
+ label: tr(
2506
+ "fluidgrids.runControl.activities.result",
2507
+ "Result (JSON, optional)"
2508
+ ),
2509
+ value: result,
2510
+ onChange: setResult,
2511
+ error: parsedResult.ok ? null : parsedResult.error,
2512
+ testId: "activity-complete-result"
2513
+ }
2514
+ ),
2515
+ action === "fail" && /* @__PURE__ */ jsxs(Fragment, { children: [
2516
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2517
+ /* @__PURE__ */ jsx(Label, { htmlFor: "activity-fail-message", children: tr(
2518
+ "fluidgrids.runControl.activities.failureMessage",
2519
+ "Failure message"
2520
+ ) }),
2521
+ /* @__PURE__ */ jsx(
2522
+ Input,
2523
+ {
2524
+ id: "activity-fail-message",
2525
+ value: failureMessage,
2526
+ onChange: (event) => setFailureMessage(event.target.value),
2527
+ "data-testid": "activity-fail-message"
2528
+ }
2529
+ )
2530
+ ] }),
2531
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
2532
+ tr(
2533
+ "fluidgrids.runControl.activities.nonRetryable",
2534
+ "Do not retry"
2535
+ ),
2536
+ /* @__PURE__ */ jsx(
2537
+ Switch,
2538
+ {
2539
+ checked: nonRetryable,
2540
+ onCheckedChange: setNonRetryable
2541
+ }
2542
+ )
2543
+ ] })
2544
+ ] }),
2545
+ (action === "complete" || action === "fail") && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
2546
+ "fluidgrids.runControl.activities.asyncOnly",
2547
+ "Only an activity that waits for an asynchronous completion can be completed or failed here."
2548
+ ) }),
2549
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "activity-action-error" })
2550
+ ] }),
2551
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
2552
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
2553
+ /* @__PURE__ */ jsx(
2554
+ Button,
2555
+ {
2556
+ onClick: () => void submit(),
2557
+ disabled: !valid || busy,
2558
+ "data-testid": "activity-action-confirm",
2559
+ children: busy ? tr("fluidgrids.runControl.common.working", "Working\u2026") : titles[action]
2560
+ }
2561
+ )
2562
+ ] })
2563
+ ]
2564
+ }
2565
+ ) });
2566
+ }
2567
+ var CUSTOM = "__custom__";
2568
+ var REJECT_CONDITIONS = [
2569
+ "NONE" /* None */,
2570
+ "NOT_OPEN" /* NotOpen */,
2571
+ "NOT_COMPLETED_CLEANLY" /* NotCompletedCleanly */
2572
+ ];
2573
+ function RunQueryPanel({ runId, handlers }) {
2574
+ const tr = useTr();
2575
+ const [choice, setChoice] = useState(handlers.names[0] ?? CUSTOM);
2576
+ const [customName, setCustomName] = useState("");
2577
+ const [args, setArgs] = useState("");
2578
+ const [rejectCondition, setRejectCondition] = useState("NONE" /* None */);
2579
+ const [running, setRunning] = useState(false);
2580
+ const [error, setError] = useState(null);
2581
+ const [answer, setAnswer] = useState(null);
2582
+ const name = choice === CUSTOM ? customName.trim() : choice;
2583
+ const parsed = parseJsonInput(args);
2584
+ const run = async () => {
2585
+ if (!name || !parsed.ok) return;
2586
+ setRunning(true);
2587
+ setError(null);
2588
+ try {
2589
+ setAnswer(
2590
+ await queryRun({ runId, name, args: parsed.value, rejectCondition })
2591
+ );
2592
+ } catch (err) {
2593
+ setAnswer(null);
2594
+ setError(describeOperationError(err));
2595
+ } finally {
2596
+ setRunning(false);
2597
+ }
2598
+ };
2599
+ return /* @__PURE__ */ jsxs(
2600
+ "section",
2601
+ {
2602
+ className: "space-y-3 rounded-lg border border-border-secondary bg-background-secondary p-4",
2603
+ "data-testid": "run-query-panel",
2604
+ children: [
2605
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2606
+ /* @__PURE__ */ jsx(
2607
+ Icon,
2608
+ {
2609
+ icon: "mdi:help-circle-outline",
2610
+ width: 18,
2611
+ className: "text-text-secondary"
2612
+ }
2613
+ ),
2614
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.query.title", "Query the run") })
2615
+ ] }),
2616
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
2617
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2618
+ /* @__PURE__ */ jsx(Label, { htmlFor: "run-query-name", children: tr("fluidgrids.runControl.query.name", "Query") }),
2619
+ handlers.names.length > 0 && /* @__PURE__ */ jsxs(
2620
+ NativeSelect,
2621
+ {
2622
+ id: "run-query-name",
2623
+ value: choice,
2624
+ onChange: setChoice,
2625
+ testId: "run-query-name",
2626
+ children: [
2627
+ handlers.names.map((handler) => /* @__PURE__ */ jsx("option", { value: handler, children: handler }, handler)),
2628
+ /* @__PURE__ */ jsx("option", { value: CUSTOM, children: tr("fluidgrids.runControl.query.other", "Another name\u2026") })
2629
+ ]
2630
+ }
2631
+ ),
2632
+ (choice === CUSTOM || handlers.names.length === 0) && /* @__PURE__ */ jsx(
2633
+ Input,
2634
+ {
2635
+ id: handlers.names.length === 0 ? "run-query-name" : void 0,
2636
+ value: customName,
2637
+ onChange: (event) => setCustomName(event.target.value),
2638
+ placeholder: tr(
2639
+ "fluidgrids.runControl.query.namePlaceholder",
2640
+ "query-name"
2641
+ ),
2642
+ "data-testid": "run-query-custom-name"
2643
+ }
2644
+ )
2645
+ ] }),
2646
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2647
+ /* @__PURE__ */ jsx(Label, { htmlFor: "run-query-reject", children: tr("fluidgrids.runControl.query.rejectCondition", "Refuse when") }),
2648
+ /* @__PURE__ */ jsxs(
2649
+ NativeSelect,
2650
+ {
2651
+ id: "run-query-reject",
2652
+ value: rejectCondition,
2653
+ onChange: (value) => setRejectCondition(
2654
+ REJECT_CONDITIONS.find((item) => item === value) ?? "NONE" /* None */
2655
+ ),
2656
+ testId: "run-query-reject",
2657
+ children: [
2658
+ /* @__PURE__ */ jsx("option", { value: "NONE" /* None */, children: tr("fluidgrids.runControl.query.rejectNone", "Never") }),
2659
+ /* @__PURE__ */ jsx("option", { value: "NOT_OPEN" /* NotOpen */, children: tr(
2660
+ "fluidgrids.runControl.query.rejectNotOpen",
2661
+ "The run has ended"
2662
+ ) }),
2663
+ /* @__PURE__ */ jsx("option", { value: "NOT_COMPLETED_CLEANLY" /* NotCompletedCleanly */, children: tr(
2664
+ "fluidgrids.runControl.query.rejectNotClean",
2665
+ "The run did not complete cleanly"
2666
+ ) })
2667
+ ]
2668
+ }
2669
+ )
2670
+ ] })
2671
+ ] }),
2672
+ /* @__PURE__ */ jsx(
2673
+ JsonField,
2674
+ {
2675
+ id: "run-query-args",
2676
+ label: tr(
2677
+ "fluidgrids.runControl.query.args",
2678
+ "Arguments (JSON, optional)"
2679
+ ),
2680
+ value: args,
2681
+ onChange: setArgs,
2682
+ rows: 2,
2683
+ error: parsed.ok ? null : parsed.error,
2684
+ testId: "run-query-args"
2685
+ }
2686
+ ),
2687
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
2688
+ Button,
2689
+ {
2690
+ size: "sm",
2691
+ onClick: () => void run(),
2692
+ disabled: !name || !parsed.ok || running,
2693
+ "data-testid": "run-query-send",
2694
+ children: [
2695
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 16 }),
2696
+ running ? tr("fluidgrids.runControl.query.running", "Asking\u2026") : tr("fluidgrids.runControl.query.run", "Run query")
2697
+ ]
2698
+ }
2699
+ ) }),
2700
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-query-error" }),
2701
+ answer && /* @__PURE__ */ jsx("div", { className: "space-y-2", "data-testid": "run-query-answer", children: answer.rejected ? /* @__PURE__ */ jsxs(StatusPill, { tone: "warning", testId: "run-query-rejected", children: [
2702
+ tr("fluidgrids.runControl.query.rejected", "Refused"),
2703
+ answer.error ? `: ${answer.error}` : ""
2704
+ ] }) : answer.error ? /* @__PURE__ */ jsx(FieldError, { message: answer.error }) : /* @__PURE__ */ jsx(JsonBlock, { value: answer.value, testId: "run-query-value" }) })
2705
+ ]
2706
+ }
2707
+ );
2708
+ }
2709
+ function entriesOf(value) {
2710
+ if (typeof value !== "object" || value === null || Array.isArray(value))
2711
+ return [];
2712
+ return Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
2713
+ }
2714
+ function display(value) {
2715
+ if (Array.isArray(value)) return value.map(String).join(", ");
2716
+ if (typeof value === "string") return value;
2717
+ return JSON.stringify(value);
2718
+ }
2719
+ function RunSearchAttributesCard({
2720
+ runId,
2721
+ attributes,
2722
+ canEdit,
2723
+ onChanged
2724
+ }) {
2725
+ const tr = useTr();
2726
+ const entries = entriesOf(attributes);
2727
+ const [editing, setEditing] = useState(false);
2728
+ const [text2, setText] = useState("");
2729
+ const [busy, setBusy] = useState(false);
2730
+ const [error, setError] = useState(null);
2731
+ const parsed = parseJsonObjectInput(text2);
2732
+ if (entries.length === 0 && !canEdit) return null;
2733
+ const save = async () => {
2734
+ if (!parsed.ok) return;
2735
+ setBusy(true);
2736
+ setError(null);
2737
+ try {
2738
+ await upsertRunSearchAttributes(runId, parsed.value);
2739
+ toast.success(
2740
+ tr(
2741
+ "fluidgrids.runControl.searchAttributes.saved",
2742
+ "Search attributes saved"
2743
+ )
2744
+ );
2745
+ setEditing(false);
2746
+ onChanged();
2747
+ } catch (err) {
2748
+ setError(describeOperationError(err));
2749
+ } finally {
2750
+ setBusy(false);
2751
+ }
2752
+ };
2753
+ return /* @__PURE__ */ jsxs(
2754
+ "section",
2755
+ {
2756
+ className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary p-4",
2757
+ "data-testid": "run-search-attributes",
2758
+ children: [
2759
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
2760
+ /* @__PURE__ */ jsx(
2761
+ Icon,
2762
+ {
2763
+ icon: "mdi:tag-search-outline",
2764
+ width: 18,
2765
+ className: "text-text-secondary"
2766
+ }
2767
+ ),
2768
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr(
2769
+ "fluidgrids.runControl.searchAttributes.title",
2770
+ "Search attributes"
2771
+ ) }),
2772
+ canEdit && !editing && /* @__PURE__ */ jsx(
2773
+ Button,
2774
+ {
2775
+ size: "sm",
2776
+ variant: "outline",
2777
+ className: "ml-auto",
2778
+ onClick: () => {
2779
+ setText("");
2780
+ setEditing(true);
2781
+ },
2782
+ "data-testid": "run-search-attributes-edit",
2783
+ children: tr(
2784
+ "fluidgrids.runControl.searchAttributes.edit",
2785
+ "Set attributes"
2786
+ )
2787
+ }
2788
+ )
2789
+ ] }),
2790
+ entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
2791
+ "fluidgrids.runControl.searchAttributes.none",
2792
+ "This run has no search attributes."
2793
+ ) }) : /* @__PURE__ */ jsx("dl", { className: "grid gap-x-4 gap-y-1 text-sm sm:grid-cols-[max-content_1fr]", children: entries.map(([name, value]) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
2794
+ /* @__PURE__ */ jsx("dt", { className: "font-mono text-xs text-text-secondary", children: name }),
2795
+ /* @__PURE__ */ jsx(
2796
+ "dd",
2797
+ {
2798
+ className: "break-words text-text-primary",
2799
+ "data-testid": `run-search-attribute-${name}`,
2800
+ children: display(value)
2801
+ }
2802
+ )
2803
+ ] }, name)) }),
2804
+ editing && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
2805
+ /* @__PURE__ */ jsx(
2806
+ JsonField,
2807
+ {
2808
+ id: "run-search-attributes-input",
2809
+ label: tr(
2810
+ "fluidgrids.runControl.searchAttributes.input",
2811
+ "Attributes to set (JSON; null removes one)"
2812
+ ),
2813
+ value: text2,
2814
+ onChange: setText,
2815
+ placeholder: '{"customerId": "c-42", "priorityTier": null}',
2816
+ error: text2.trim() && !parsed.ok ? parsed.error : null,
2817
+ testId: "run-search-attributes-input"
2818
+ }
2819
+ ),
2820
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-search-attributes-error" }),
2821
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2822
+ /* @__PURE__ */ jsx(
2823
+ Button,
2824
+ {
2825
+ size: "sm",
2826
+ variant: "outline",
2827
+ onClick: () => setEditing(false),
2828
+ disabled: busy,
2829
+ children: tr("fluidgrids.runControl.common.cancel", "Cancel")
2830
+ }
2831
+ ),
2832
+ /* @__PURE__ */ jsx(
2833
+ Button,
2834
+ {
2835
+ size: "sm",
2836
+ onClick: () => void save(),
2837
+ disabled: busy || !text2.trim() || !parsed.ok,
2838
+ "data-testid": "run-search-attributes-save",
2839
+ children: tr("fluidgrids.runControl.common.save", "Save")
2840
+ }
2841
+ )
2842
+ ] })
2843
+ ] })
2844
+ ]
2845
+ }
2846
+ );
2847
+ }
2848
+ var UPDATE_POLL_INTERVAL_MS = 1e3;
2849
+ var UPDATE_POLL_LIMIT = 30;
2850
+ var CUSTOM2 = "__custom__";
2851
+ function statusTone(status) {
2852
+ if (status === "COMPLETED") return "success";
2853
+ if (status === "REJECTED") return "error";
2854
+ return "info";
2855
+ }
2856
+ function RunUpdateDialog(props) {
2857
+ return /* @__PURE__ */ jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: props.open && /* @__PURE__ */ jsx(RunUpdateForm, { ...props }) });
2858
+ }
2859
+ function RunUpdateForm({
2860
+ runId,
2861
+ handlers,
2862
+ open,
2863
+ onOpenChange,
2864
+ onDone
2865
+ }) {
2866
+ const tr = useTr();
2867
+ const [choice, setChoice] = useState(handlers.names[0] ?? CUSTOM2);
2868
+ const [customName, setCustomName] = useState("");
2869
+ const [args, setArgs] = useState("");
2870
+ const [waitFor, setWaitFor] = useState(
2871
+ "COMPLETED" /* Completed */
2872
+ );
2873
+ const [updateId] = useState(() => newRunControlId("ui-update"));
2874
+ const [sending, setSending] = useState(false);
2875
+ const [error, setError] = useState(null);
2876
+ const [result, setResult] = useState(null);
2877
+ const polls = useRef(0);
2878
+ useEffect(() => {
2879
+ if (!open || !result || isFinalUpdateStatus(result.status)) return;
2880
+ if (polls.current >= UPDATE_POLL_LIMIT) return;
2881
+ const timer = setTimeout(() => {
2882
+ polls.current += 1;
2883
+ fetchRunUpdate(runId, result.updateId).then((next) => {
2884
+ setResult(next);
2885
+ if (isFinalUpdateStatus(next.status)) onDone?.(next);
2886
+ }).catch((err) => setError(describeOperationError(err)));
2887
+ }, UPDATE_POLL_INTERVAL_MS);
2888
+ return () => clearTimeout(timer);
2889
+ }, [open, result, runId, onDone]);
2890
+ const name = choice === CUSTOM2 ? customName.trim() : choice;
2891
+ const parsed = parseJsonInput(args);
2892
+ const canSend = !!name && parsed.ok && !sending;
2893
+ const send = async () => {
2894
+ if (!canSend || !parsed.ok) return;
2895
+ setSending(true);
2896
+ setError(null);
2897
+ try {
2898
+ const answer = await sendRunUpdate({
2899
+ runId,
2900
+ name,
2901
+ args: parsed.value,
2902
+ updateId,
2903
+ waitFor
2904
+ });
2905
+ polls.current = 0;
2906
+ setResult(answer);
2907
+ if (updateReached(answer.status, waitFor)) onDone?.(answer);
2908
+ } catch (err) {
2909
+ setError(describeOperationError(err));
2910
+ } finally {
2911
+ setSending(false);
2912
+ }
2913
+ };
2914
+ return /* @__PURE__ */ jsxs(
2915
+ DialogContent,
2916
+ {
2917
+ style: RUN_CONTROL_DIALOG_STYLE,
2918
+ className: "max-w-lg",
2919
+ "data-testid": "run-update-dialog",
2920
+ children: [
2921
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2922
+ /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.update.title", "Send an update") }),
2923
+ /* @__PURE__ */ jsx(DialogDescription, { children: tr(
2924
+ "fluidgrids.runControl.update.description",
2925
+ "The run's update handler validates and applies it. A rejected update leaves no history."
2926
+ ) })
2927
+ ] }),
2928
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2929
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2930
+ /* @__PURE__ */ jsx(Label, { htmlFor: "run-update-name", children: tr("fluidgrids.runControl.update.name", "Update") }),
2931
+ handlers.names.length > 0 && /* @__PURE__ */ jsxs(
2932
+ NativeSelect,
2933
+ {
2934
+ id: "run-update-name",
2935
+ value: choice,
2936
+ onChange: setChoice,
2937
+ testId: "run-update-name",
2938
+ children: [
2939
+ handlers.names.map((handler) => /* @__PURE__ */ jsx("option", { value: handler, children: handler }, handler)),
2940
+ handlers.openNames && /* @__PURE__ */ jsx("option", { value: CUSTOM2, children: tr("fluidgrids.runControl.update.other", "Another name\u2026") })
2941
+ ]
2942
+ }
2943
+ ),
2944
+ (choice === CUSTOM2 || handlers.names.length === 0) && /* @__PURE__ */ jsx(
2945
+ Input,
2946
+ {
2947
+ id: handlers.names.length === 0 ? "run-update-name" : void 0,
2948
+ value: customName,
2949
+ onChange: (event) => setCustomName(event.target.value),
2950
+ placeholder: tr(
2951
+ "fluidgrids.runControl.update.namePlaceholder",
2952
+ "update-name"
2953
+ ),
2954
+ "data-testid": "run-update-custom-name"
2955
+ }
2956
+ )
2957
+ ] }),
2958
+ /* @__PURE__ */ jsx(
2959
+ JsonField,
2960
+ {
2961
+ id: "run-update-args",
2962
+ label: tr("fluidgrids.runControl.update.args", "Arguments (JSON)"),
2963
+ value: args,
2964
+ onChange: setArgs,
2965
+ placeholder: '{"quantity": 2}',
2966
+ error: parsed.ok ? null : parsed.error,
2967
+ testId: "run-update-args"
2968
+ }
2969
+ ),
2970
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2971
+ /* @__PURE__ */ jsx(Label, { htmlFor: "run-update-wait", children: tr("fluidgrids.runControl.update.waitFor", "Wait until") }),
2972
+ /* @__PURE__ */ jsxs(
2973
+ NativeSelect,
2974
+ {
2975
+ id: "run-update-wait",
2976
+ value: waitFor,
2977
+ onChange: (value) => setWaitFor(
2978
+ value === "ACCEPTED" /* Accepted */ ? "ACCEPTED" /* Accepted */ : "COMPLETED" /* Completed */
2979
+ ),
2980
+ testId: "run-update-wait",
2981
+ children: [
2982
+ /* @__PURE__ */ jsx("option", { value: "COMPLETED" /* Completed */, children: tr("fluidgrids.runControl.update.waitCompleted", "it completes") }),
2983
+ /* @__PURE__ */ jsx("option", { value: "ACCEPTED" /* Accepted */, children: tr(
2984
+ "fluidgrids.runControl.update.waitAccepted",
2985
+ "it is accepted"
2986
+ ) })
2987
+ ]
2988
+ }
2989
+ )
2990
+ ] }),
2991
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-update-error" }),
2992
+ result && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "run-update-result", children: [
2993
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
2994
+ /* @__PURE__ */ jsx(
2995
+ StatusPill,
2996
+ {
2997
+ tone: statusTone(result.status),
2998
+ testId: "run-update-status",
2999
+ children: result.status
3000
+ }
3001
+ ),
3002
+ /* @__PURE__ */ jsx("code", { className: "truncate font-mono text-xs text-text-secondary", children: result.updateId })
3003
+ ] }),
3004
+ result.status === "COMPLETED" && result.result !== void 0 && /* @__PURE__ */ jsx(JsonBlock, { value: result.result, testId: "run-update-value" }),
3005
+ result.failure && /* @__PURE__ */ jsx(JsonBlock, { value: result.failure, testId: "run-update-failure" })
3006
+ ] })
3007
+ ] }),
3008
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
3009
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: tr("fluidgrids.runControl.common.close", "Close") }),
3010
+ /* @__PURE__ */ jsx(
3011
+ Button,
3012
+ {
3013
+ onClick: () => void send(),
3014
+ disabled: !canSend,
3015
+ "data-testid": "run-update-send",
3016
+ children: sending ? tr("fluidgrids.runControl.update.sending", "Sending\u2026") : tr("fluidgrids.runControl.update.send", "Send update")
3017
+ }
3018
+ )
3019
+ ] })
3020
+ ]
3021
+ }
3022
+ );
3023
+ }
3024
+ function ResetRunDialog(props) {
3025
+ return /* @__PURE__ */ jsx(AlertDialog, { open: props.point !== null, onOpenChange: props.onOpenChange, children: props.point && /* @__PURE__ */ jsx(ResetRunForm, { ...props, point: props.point }) });
3026
+ }
3027
+ function ResetRunForm({
3028
+ runId,
3029
+ point,
3030
+ onOpenChange,
3031
+ onReset
3032
+ }) {
3033
+ const tr = useTr();
3034
+ const [reason, setReason] = useState("");
3035
+ const [reapplySignals, setReapplySignals] = useState(true);
3036
+ const [reapplyUpdates, setReapplyUpdates] = useState(true);
3037
+ const [busy, setBusy] = useState(false);
3038
+ const [error, setError] = useState(null);
3039
+ const confirm = async () => {
3040
+ setBusy(true);
3041
+ setError(null);
3042
+ try {
3043
+ const fork = point.kind === "lastClean" ? await resetRunToLastCleanTask({ runId, reason }) : await resetRun({
3044
+ runId,
3045
+ to: { seq: point.seq },
3046
+ reason,
3047
+ reapplySignals,
3048
+ reapplyUpdates
3049
+ });
3050
+ onOpenChange(false);
3051
+ onReset(fork.id);
3052
+ } catch (err) {
3053
+ setError(describeOperationError(err));
3054
+ } finally {
3055
+ setBusy(false);
3056
+ }
3057
+ };
3058
+ return /* @__PURE__ */ jsxs(
3059
+ AlertDialogContent,
3060
+ {
3061
+ style: RUN_CONTROL_DIALOG_STYLE,
3062
+ "data-testid": "run-reset-dialog",
3063
+ children: [
3064
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
3065
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: point.kind === "lastClean" ? tr(
3066
+ "fluidgrids.runControl.reset.lastCleanTitle",
3067
+ "Reset to the last clean task?"
3068
+ ) : tr(
3069
+ "fluidgrids.runControl.reset.hereTitle",
3070
+ "Reset at event #{{seq}}?",
3071
+ {
3072
+ seq: point.seq
3073
+ }
3074
+ ) }),
3075
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: tr(
3076
+ "fluidgrids.runControl.reset.description",
3077
+ "A new run with the same workflow id replays this run's history up to that point and continues from there. This run is terminated with the reason RESET."
3078
+ ) })
3079
+ ] }),
3080
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3081
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3082
+ /* @__PURE__ */ jsx(Label, { htmlFor: "run-reset-reason", children: tr("fluidgrids.runControl.reset.reason", "Reason (optional)") }),
3083
+ /* @__PURE__ */ jsx(
3084
+ Input,
3085
+ {
3086
+ id: "run-reset-reason",
3087
+ value: reason,
3088
+ maxLength: 1e3,
3089
+ onChange: (event) => setReason(event.target.value),
3090
+ "data-testid": "run-reset-reason"
3091
+ }
3092
+ )
3093
+ ] }),
3094
+ point.kind === "seq" && /* @__PURE__ */ jsxs(Fragment, { children: [
3095
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-text-primary", children: [
3096
+ tr(
3097
+ "fluidgrids.runControl.reset.reapplySignals",
3098
+ "Re-apply the signals received after it"
3099
+ ),
3100
+ /* @__PURE__ */ jsx(
3101
+ Switch,
3102
+ {
3103
+ checked: reapplySignals,
3104
+ onCheckedChange: setReapplySignals,
3105
+ "data-testid": "run-reset-reapply-signals"
3106
+ }
3107
+ )
3108
+ ] }),
3109
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-text-primary", children: [
3110
+ tr(
3111
+ "fluidgrids.runControl.reset.reapplyUpdates",
3112
+ "Re-apply the updates received after it"
3113
+ ),
3114
+ /* @__PURE__ */ jsx(
3115
+ Switch,
3116
+ {
3117
+ checked: reapplyUpdates,
3118
+ onCheckedChange: setReapplyUpdates,
3119
+ "data-testid": "run-reset-reapply-updates"
3120
+ }
3121
+ )
3122
+ ] })
3123
+ ] }),
3124
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-reset-error" })
3125
+ ] }),
3126
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
3127
+ /* @__PURE__ */ jsx(AlertDialogCancel, { disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
3128
+ /* @__PURE__ */ jsx(
3129
+ AlertDialogAction,
3130
+ {
3131
+ onClick: (event) => {
3132
+ event.preventDefault();
3133
+ void confirm();
3134
+ },
3135
+ disabled: busy,
3136
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
3137
+ "data-testid": "run-reset-confirm",
3138
+ children: busy ? tr("fluidgrids.runControl.reset.resetting", "Resetting\u2026") : tr("fluidgrids.runControl.reset.confirm", "Reset run")
3139
+ }
3140
+ )
3141
+ ] })
3142
+ ]
3143
+ }
3144
+ );
3145
+ }
3146
+ var SIGNAL_TOKEN_TTLS = [
3147
+ { seconds: 3600, key: "hour" },
3148
+ { seconds: 86400, key: "day" },
3149
+ { seconds: 604800, key: "week" },
3150
+ { seconds: 2592e3, key: "month" },
3151
+ { seconds: 7776e3, key: "quarter" }
3152
+ ];
3153
+ function signalTokenTtlLabel(tr, key) {
3154
+ switch (key) {
3155
+ case "hour":
3156
+ return tr("fluidgrids.runControl.tokens.ttlOption.hour", "1 hour");
3157
+ case "day":
3158
+ return tr("fluidgrids.runControl.tokens.ttlOption.day", "1 day");
3159
+ case "week":
3160
+ return tr("fluidgrids.runControl.tokens.ttlOption.week", "7 days");
3161
+ case "month":
3162
+ return tr("fluidgrids.runControl.tokens.ttlOption.month", "30 days");
3163
+ case "quarter":
3164
+ return tr("fluidgrids.runControl.tokens.ttlOption.quarter", "90 days");
3165
+ }
3166
+ }
3167
+ function signalTokenStateLabel(tr, state) {
3168
+ switch (state) {
3169
+ case "active":
3170
+ return tr("fluidgrids.runControl.tokens.state.active", "Active");
3171
+ case "spent":
3172
+ return tr("fluidgrids.runControl.tokens.state.spent", "Spent");
3173
+ case "expired":
3174
+ return tr("fluidgrids.runControl.tokens.state.expired", "Expired");
3175
+ case "revoked":
3176
+ return tr("fluidgrids.runControl.tokens.state.revoked", "Revoked");
3177
+ }
3178
+ }
3179
+ function parseIpAllowlist(text2) {
3180
+ const values = text2.split(/[\s,]+/).map((item) => item.trim()).filter(Boolean);
3181
+ const pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/;
3182
+ for (const value of values) {
3183
+ const match = pattern.exec(value);
3184
+ const octets = match ? match.slice(1, 5).map(Number) : [];
3185
+ const prefix = match?.[6] === void 0 ? 32 : Number(match[6]);
3186
+ if (!match || octets.some((octet) => octet > 255) || prefix > 32)
3187
+ return { ok: false, bad: value };
3188
+ }
3189
+ return { ok: true, values: values.slice(0, 50) };
3190
+ }
3191
+ function tokenState(token) {
3192
+ if (token.revokedAt) return "revoked";
3193
+ if (!token.reusable && token.usedAt) return "spent";
3194
+ if (new Date(token.expiresAt).getTime() <= Date.now()) return "expired";
3195
+ return "active";
3196
+ }
3197
+ function SignalTokensPanel({
3198
+ runId,
3199
+ signalNames,
3200
+ canCreate
3201
+ }) {
3202
+ const tr = useTr();
3203
+ const tokens = useResource(
3204
+ `signal-tokens:${runId}`,
3205
+ true,
3206
+ () => fetchSignalTokens(runId)
3207
+ );
3208
+ const [creating, setCreating] = useState(false);
3209
+ const [created, setCreated] = useState(
3210
+ null
3211
+ );
3212
+ const revoke = async (id) => {
3213
+ try {
3214
+ await revokeSignalToken(id);
3215
+ toast.success(
3216
+ tr("fluidgrids.runControl.tokens.revoked", "Token revoked")
3217
+ );
3218
+ await tokens.refetch();
3219
+ } catch (err) {
3220
+ toast.error(describeOperationError(err));
3221
+ }
3222
+ };
3223
+ return /* @__PURE__ */ jsxs(
3224
+ "section",
3225
+ {
3226
+ className: "space-y-3 rounded-lg border border-border-secondary bg-background-secondary p-4",
3227
+ "data-testid": "signal-tokens-panel",
3228
+ children: [
3229
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3230
+ /* @__PURE__ */ jsx(
3231
+ Icon,
3232
+ {
3233
+ icon: "mdi:key-chain-variant",
3234
+ width: 18,
3235
+ className: "text-text-secondary"
3236
+ }
3237
+ ),
3238
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.tokens.title", "Signal tokens") }),
3239
+ canCreate && /* @__PURE__ */ jsxs(
3240
+ Button,
3241
+ {
3242
+ size: "sm",
3243
+ variant: "outline",
3244
+ className: "ml-auto",
3245
+ onClick: () => setCreating(true),
3246
+ "data-testid": "signal-token-create",
3247
+ children: [
3248
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", className: "mr-1", width: 14 }),
3249
+ tr("fluidgrids.runControl.tokens.create", "New token")
3250
+ ]
3251
+ }
3252
+ )
3253
+ ] }),
3254
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
3255
+ "fluidgrids.runControl.tokens.description",
3256
+ "A token lets another system send a signal to this run with a plain POST, without a FluidGrids credential."
3257
+ ) }),
3258
+ /* @__PURE__ */ jsx(FieldError, { message: tokens.error }),
3259
+ tokens.data && tokens.data.length === 0 && /* @__PURE__ */ jsx(
3260
+ "p",
3261
+ {
3262
+ className: "text-sm text-text-secondary",
3263
+ "data-testid": "signal-tokens-empty",
3264
+ children: tr("fluidgrids.runControl.tokens.empty", "No tokens yet.")
3265
+ }
3266
+ ),
3267
+ tokens.data && tokens.data.length > 0 && /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border-secondary rounded-md border border-border-secondary", children: tokens.data.map((token) => {
3268
+ const state = tokenState(token);
3269
+ return /* @__PURE__ */ jsxs(
3270
+ "li",
3271
+ {
3272
+ className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
3273
+ "data-testid": `signal-token-${token.id}`,
3274
+ children: [
3275
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-text-primary", children: token.signalName }),
3276
+ /* @__PURE__ */ jsx(StatusPill, { tone: state === "active" ? "success" : "neutral", children: signalTokenStateLabel(tr, state) }),
3277
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-text-secondary", children: [
3278
+ token.reusable ? tr("fluidgrids.runControl.tokens.reusable", "Reusable") : tr(
3279
+ "fluidgrids.runControl.tokens.singleUse",
3280
+ "Single use"
3281
+ ),
3282
+ " \xB7 ",
3283
+ tr(
3284
+ "fluidgrids.runControl.tokens.expires",
3285
+ "expires {{date}}",
3286
+ {
3287
+ date: new Date(token.expiresAt).toLocaleString()
3288
+ }
3289
+ )
3290
+ ] }),
3291
+ state === "active" && canCreate && /* @__PURE__ */ jsx(
3292
+ Button,
3293
+ {
3294
+ size: "sm",
3295
+ variant: "ghost",
3296
+ className: "ml-auto text-[var(--color-status-error-text)]",
3297
+ onClick: () => void revoke(token.id),
3298
+ "data-testid": `signal-token-revoke-${token.id}`,
3299
+ children: tr("fluidgrids.runControl.tokens.revoke", "Revoke")
3300
+ }
3301
+ )
3302
+ ]
3303
+ },
3304
+ token.id
3305
+ );
3306
+ }) }),
3307
+ creating && /* @__PURE__ */ jsx(
3308
+ CreateSignalTokenDialog,
3309
+ {
3310
+ runId,
3311
+ signalNames,
3312
+ onClose: () => setCreating(false),
3313
+ onCreated: (next) => {
3314
+ setCreating(false);
3315
+ setCreated(next);
3316
+ void tokens.refetch();
3317
+ }
3318
+ }
3319
+ ),
3320
+ created && /* @__PURE__ */ jsx(
3321
+ SignalTokenCreatedDialog,
3322
+ {
3323
+ created,
3324
+ onClose: () => setCreated(null)
3325
+ }
3326
+ )
3327
+ ]
3328
+ }
3329
+ );
3330
+ }
3331
+ function CreateSignalTokenDialog({
3332
+ runId,
3333
+ signalNames,
3334
+ onClose,
3335
+ onCreated
3336
+ }) {
3337
+ const tr = useTr();
3338
+ const [signalName, setSignalName] = useState(signalNames[0] ?? "");
3339
+ const [ttl, setTtl] = useState(String(2592e3));
3340
+ const [reusable, setReusable] = useState(false);
3341
+ const [ips, setIps] = useState("");
3342
+ const [busy, setBusy] = useState(false);
3343
+ const [error, setError] = useState(null);
3344
+ const allowlist = parseIpAllowlist(ips);
3345
+ const submit = async () => {
3346
+ if (!signalName.trim() || !allowlist.ok) return;
3347
+ setBusy(true);
3348
+ setError(null);
3349
+ try {
3350
+ onCreated(
3351
+ await createSignalToken({
3352
+ runId,
3353
+ signalName: signalName.trim(),
3354
+ ttlSeconds: Number(ttl),
3355
+ reusable,
3356
+ ipAllowlist: allowlist.values
3357
+ })
3358
+ );
3359
+ } catch (err) {
3360
+ setError(describeOperationError(err));
3361
+ } finally {
3362
+ setBusy(false);
3363
+ }
3364
+ };
3365
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
3366
+ DialogContent,
3367
+ {
3368
+ style: RUN_CONTROL_DIALOG_STYLE,
3369
+ "data-testid": "signal-token-dialog",
3370
+ children: [
3371
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3372
+ /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.tokens.createTitle", "New signal token") }),
3373
+ /* @__PURE__ */ jsx(DialogDescription, { children: tr(
3374
+ "fluidgrids.runControl.tokens.createDescription",
3375
+ "The token is shown once. Store it where the sending system can read it."
3376
+ ) })
3377
+ ] }),
3378
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3379
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3380
+ /* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-name", children: tr("fluidgrids.runControl.tokens.signal", "Signal") }),
3381
+ signalNames.length > 0 ? /* @__PURE__ */ jsx(
3382
+ NativeSelect,
3383
+ {
3384
+ id: "signal-token-name",
3385
+ value: signalName,
3386
+ onChange: setSignalName,
3387
+ testId: "signal-token-name",
3388
+ children: signalNames.map((name) => /* @__PURE__ */ jsx("option", { value: name, children: name }, name))
3389
+ }
3390
+ ) : /* @__PURE__ */ jsx(
3391
+ Input,
3392
+ {
3393
+ id: "signal-token-name",
3394
+ value: signalName,
3395
+ onChange: (event) => setSignalName(event.target.value),
3396
+ "data-testid": "signal-token-name"
3397
+ }
3398
+ )
3399
+ ] }),
3400
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3401
+ /* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-ttl", children: tr("fluidgrids.runControl.tokens.ttl", "Lifetime") }),
3402
+ /* @__PURE__ */ jsx(
3403
+ NativeSelect,
3404
+ {
3405
+ id: "signal-token-ttl",
3406
+ value: ttl,
3407
+ onChange: setTtl,
3408
+ testId: "signal-token-ttl",
3409
+ children: SIGNAL_TOKEN_TTLS.map((option) => /* @__PURE__ */ jsx("option", { value: String(option.seconds), children: signalTokenTtlLabel(tr, option.key) }, option.seconds))
3410
+ }
3411
+ )
3412
+ ] }),
3413
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
3414
+ tr(
3415
+ "fluidgrids.runControl.tokens.reusableToggle",
3416
+ "Reusable until it expires"
3417
+ ),
3418
+ /* @__PURE__ */ jsx(
3419
+ Switch,
3420
+ {
3421
+ checked: reusable,
3422
+ onCheckedChange: setReusable,
3423
+ "data-testid": "signal-token-reusable"
3424
+ }
3425
+ )
3426
+ ] }),
3427
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3428
+ /* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-ips", children: tr(
3429
+ "fluidgrids.runControl.tokens.ips",
3430
+ "Allowed IPv4 addresses or ranges (optional)"
3431
+ ) }),
3432
+ /* @__PURE__ */ jsx(
3433
+ Textarea,
3434
+ {
3435
+ id: "signal-token-ips",
3436
+ rows: 2,
3437
+ value: ips,
3438
+ onChange: (event) => setIps(event.target.value),
3439
+ placeholder: "203.0.113.10, 198.51.100.0/24",
3440
+ className: "font-mono text-xs",
3441
+ "data-testid": "signal-token-ips"
3442
+ }
3443
+ ),
3444
+ !allowlist.ok && /* @__PURE__ */ jsx(
3445
+ FieldError,
3446
+ {
3447
+ message: tr(
3448
+ "fluidgrids.runControl.tokens.badIp",
3449
+ "Not an IPv4 address or range: {{value}}",
3450
+ { value: allowlist.bad }
3451
+ )
3452
+ }
3453
+ )
3454
+ ] }),
3455
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "signal-token-error" })
3456
+ ] }),
3457
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
3458
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
3459
+ /* @__PURE__ */ jsx(
3460
+ Button,
3461
+ {
3462
+ onClick: () => void submit(),
3463
+ disabled: busy || !signalName.trim() || !allowlist.ok,
3464
+ "data-testid": "signal-token-submit",
3465
+ children: tr("fluidgrids.runControl.tokens.createButton", "Create token")
3466
+ }
3467
+ )
3468
+ ] })
3469
+ ]
3470
+ }
3471
+ ) });
3472
+ }
3473
+ function SignalTokenCreatedDialog({
3474
+ created,
3475
+ onClose
3476
+ }) {
3477
+ const tr = useTr();
3478
+ const copy = async (text2) => {
3479
+ try {
3480
+ await navigator.clipboard.writeText(text2);
3481
+ toast.success(tr("fluidgrids.runControl.common.copied", "Copied"));
3482
+ } catch {
3483
+ toast.error(
3484
+ tr(
3485
+ "fluidgrids.runControl.common.copyFailed",
3486
+ "Could not copy; select the text instead."
3487
+ )
3488
+ );
3489
+ }
3490
+ };
3491
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
3492
+ DialogContent,
3493
+ {
3494
+ style: RUN_CONTROL_DIALOG_STYLE,
3495
+ "data-testid": "signal-token-created",
3496
+ children: [
3497
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3498
+ /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.tokens.createdTitle", "Token created") }),
3499
+ /* @__PURE__ */ jsx(DialogDescription, { children: tr(
3500
+ "fluidgrids.runControl.tokens.createdDescription",
3501
+ "Copy it now: it cannot be shown again. POST the signal's payload as JSON to the URL."
3502
+ ) })
3503
+ ] }),
3504
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3505
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3506
+ /* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.runControl.tokens.url", "Signal URL") }),
3507
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3508
+ /* @__PURE__ */ jsx(
3509
+ "code",
3510
+ {
3511
+ className: "min-w-0 flex-1 break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
3512
+ "data-testid": "signal-token-url",
3513
+ children: created.url
3514
+ }
3515
+ ),
3516
+ /* @__PURE__ */ jsx(
3517
+ Button,
3518
+ {
3519
+ size: "sm",
3520
+ variant: "outline",
3521
+ onClick: () => void copy(created.url),
3522
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 })
3523
+ }
3524
+ )
3525
+ ] })
3526
+ ] }),
3527
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3528
+ /* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.runControl.tokens.token", "Token") }),
3529
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3530
+ /* @__PURE__ */ jsx(
3531
+ "code",
3532
+ {
3533
+ className: "min-w-0 flex-1 break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
3534
+ "data-testid": "signal-token-value",
3535
+ children: created.token
3536
+ }
3537
+ ),
3538
+ /* @__PURE__ */ jsx(
3539
+ Button,
3540
+ {
3541
+ size: "sm",
3542
+ variant: "outline",
3543
+ onClick: () => void copy(created.token),
3544
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 })
3545
+ }
3546
+ )
3547
+ ] })
3548
+ ] })
3549
+ ] }),
3550
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, "data-testid": "signal-token-created-done", children: tr("fluidgrids.runControl.common.done", "Done") }) })
3551
+ ]
3552
+ }
3553
+ ) });
3554
+ }
3555
+ function shouldShowWaitingForWorker(props) {
3556
+ return !!props.taskQueue && props.taskQueue !== "managed" && props.waiting && props.pollers === 0;
3557
+ }
3558
+ function WaitingForWorkerBanner(props) {
3559
+ const tr = useTr();
3560
+ if (!shouldShowWaitingForWorker(props)) return null;
3561
+ return /* @__PURE__ */ jsxs(
3562
+ "div",
3563
+ {
3564
+ role: "status",
3565
+ className: "mb-6 flex items-start gap-2 rounded-lg border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg-subtle)] p-3 text-sm text-[var(--color-status-warning-text)]",
3566
+ "data-testid": "waiting-for-worker",
3567
+ children: [
3568
+ /* @__PURE__ */ jsx(
3569
+ Icon,
3570
+ {
3571
+ icon: "mdi:server-network-off",
3572
+ width: 18,
3573
+ className: "mt-0.5 shrink-0"
3574
+ }
3575
+ ),
3576
+ /* @__PURE__ */ jsx("span", { children: tr(
3577
+ "fluidgrids.runControl.worker.waiting",
3578
+ "Waiting for a worker on queue {{queue}}. Start a worker that serves this queue to continue the run.",
3579
+ { queue: props.taskQueue ?? "" }
3580
+ ) })
3581
+ ]
3582
+ }
3583
+ );
3584
+ }
3585
+ var QUEUE_STATS_INTERVAL_MS = 6e4;
3586
+ function isRunOpen(status) {
3587
+ return status === "RUNNING" || status === "PENDING" || status === "WAITING";
3588
+ }
3589
+ function RunControlSection({
3590
+ run,
3591
+ flags,
3592
+ canControl,
3593
+ activities,
3594
+ workflowTaskWaiting,
3595
+ historyEvents,
3596
+ signalNames,
3597
+ onChanged,
3598
+ onOpenRun,
3599
+ resetPoint,
3600
+ onResetPointChange
3601
+ }) {
3602
+ const tr = useTr();
3603
+ const durable = run.executionKind === "DURABLE_FN";
3604
+ const open = isRunOpen(run.status);
3605
+ const runControl = durable ? flags.runControlDurable : flags.runControl;
3606
+ const [updateOpen, setUpdateOpen] = useState(false);
3607
+ const [panel, setPanel] = useState(null);
3608
+ const fieldsEnabled = flags.searchAttributes || flags.selfHostedWorkers;
3609
+ const fields = useResource(
3610
+ `run-control-fields:${run.id}`,
3611
+ fieldsEnabled,
3612
+ () => fetchRunControlFields(run.id)
3613
+ );
3614
+ const activitiesShown = durable && open && flags.runControlDurable && activities.length > 0;
3615
+ const rules = useResource(
3616
+ "pause-rules",
3617
+ activitiesShown,
3618
+ fetchPauseRules
3619
+ );
3620
+ const queue = run.taskQueue ?? null;
3621
+ const waiting = workflowTaskWaiting || activities.some((activity) => activity.state === "SCHEDULED");
3622
+ const statsEnabled = flags.selfHostedWorkers && durable && open && !!queue && queue !== "managed" && waiting;
3623
+ const stats = useResource(
3624
+ `queue-stats:${queue ?? ""}`,
3625
+ statsEnabled,
3626
+ () => fetchTaskQueueStats(queue ?? "managed"),
3627
+ { live: true, intervalMs: QUEUE_STATS_INTERVAL_MS }
3628
+ );
3629
+ const updates = declaredHandlers(
3630
+ run.definitionJson,
3631
+ run.workflowFn,
3632
+ "updates"
3633
+ );
3634
+ const declaredQueries = declaredHandlers(
3635
+ run.definitionJson,
3636
+ run.workflowFn,
3637
+ "queries"
3638
+ );
3639
+ const queries = durable ? declaredQueries : { names: [...GRAPH_RUN_QUERIES], openNames: false };
3640
+ const pauseStates = activityPauseStates(historyEvents);
3641
+ const showUpdate = runControl && durable && open && canControl;
3642
+ const showReset = runControl && durable && canControl;
3643
+ const showQuery = runControl;
3644
+ const showTokens = runControl && durable && open;
3645
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "run-control-section", children: [
3646
+ (showUpdate || showReset || showQuery || showTokens) && /* @__PURE__ */ jsxs(
3647
+ "div",
3648
+ {
3649
+ className: "mb-4 flex flex-wrap items-center gap-2",
3650
+ "data-testid": "run-control-actions",
3651
+ children: [
3652
+ showUpdate && /* @__PURE__ */ jsxs(
3653
+ Button,
3654
+ {
3655
+ variant: "outline",
3656
+ size: "sm",
3657
+ onClick: () => setUpdateOpen(true),
3658
+ "data-testid": "run-update-button",
3659
+ children: [
3660
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:send-check-outline", className: "mr-1", width: 16 }),
3661
+ tr("fluidgrids.runControl.actions.update", "Update")
3662
+ ]
3663
+ }
3664
+ ),
3665
+ showQuery && /* @__PURE__ */ jsxs(
3666
+ Button,
3667
+ {
3668
+ variant: panel === "query" ? "default" : "outline",
3669
+ size: "sm",
3670
+ "aria-pressed": panel === "query",
3671
+ onClick: () => setPanel(panel === "query" ? null : "query"),
3672
+ "data-testid": "run-query-button",
3673
+ children: [
3674
+ /* @__PURE__ */ jsx(
3675
+ Icon,
3676
+ {
3677
+ icon: "mdi:help-circle-outline",
3678
+ className: "mr-1",
3679
+ width: 16
3680
+ }
3681
+ ),
3682
+ tr("fluidgrids.runControl.actions.query", "Query")
3683
+ ]
3684
+ }
3685
+ ),
3686
+ showTokens && /* @__PURE__ */ jsxs(
3687
+ Button,
3688
+ {
3689
+ variant: panel === "tokens" ? "default" : "outline",
3690
+ size: "sm",
3691
+ "aria-pressed": panel === "tokens",
3692
+ onClick: () => setPanel(panel === "tokens" ? null : "tokens"),
3693
+ "data-testid": "run-tokens-button",
3694
+ children: [
3695
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:key-chain-variant", className: "mr-1", width: 16 }),
3696
+ tr("fluidgrids.runControl.actions.tokens", "Signal tokens")
3697
+ ]
3698
+ }
3699
+ ),
3700
+ showReset && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
3701
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
3702
+ Button,
3703
+ {
3704
+ variant: "outline",
3705
+ size: "sm",
3706
+ "data-testid": "run-reset-menu",
3707
+ children: [
3708
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:restore", className: "mr-1", width: 16 }),
3709
+ tr("fluidgrids.runControl.actions.reset", "Reset"),
3710
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:chevron-down", className: "ml-1", width: 14 })
3711
+ ]
3712
+ }
3713
+ ) }),
3714
+ /* @__PURE__ */ jsx(DropdownMenuContent, { style: RUN_CONTROL_DIALOG_STYLE, children: /* @__PURE__ */ jsx(
3715
+ DropdownMenuItem,
3716
+ {
3717
+ onSelect: () => onResetPointChange({ kind: "lastClean" }),
3718
+ "data-testid": "run-reset-last-clean",
3719
+ children: tr(
3720
+ "fluidgrids.runControl.actions.resetLastClean",
3721
+ "Reset to last clean task"
3722
+ )
3723
+ }
3724
+ ) })
3725
+ ] }),
3726
+ showReset && /* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: tr(
3727
+ "fluidgrids.runControl.actions.resetHint",
3728
+ "Or use Reset here on an event in History."
3729
+ ) })
3730
+ ]
3731
+ }
3732
+ ),
3733
+ statsEnabled && /* @__PURE__ */ jsx(
3734
+ WaitingForWorkerBanner,
3735
+ {
3736
+ taskQueue: queue,
3737
+ waiting,
3738
+ pollers: stats.data?.pollers ?? null
3739
+ }
3740
+ ),
3741
+ panel === "query" && showQuery && /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(RunQueryPanel, { runId: run.id, handlers: queries }) }),
3742
+ panel === "tokens" && showTokens && /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(
3743
+ SignalTokensPanel,
3744
+ {
3745
+ runId: run.id,
3746
+ signalNames,
3747
+ canCreate: canControl
3748
+ }
3749
+ ) }),
3750
+ flags.searchAttributes && fields.data && /* @__PURE__ */ jsx(
3751
+ RunSearchAttributesCard,
3752
+ {
3753
+ runId: run.id,
3754
+ attributes: fields.data.searchAttributes,
3755
+ canEdit: canControl,
3756
+ onChanged: () => void fields.refetch()
3757
+ }
3758
+ ),
3759
+ activitiesShown && /* @__PURE__ */ jsx(
3760
+ RunActivitiesCard,
3761
+ {
3762
+ runId: run.id,
3763
+ activities,
3764
+ pauseStates,
3765
+ pauseRules: rules.data ?? [],
3766
+ canControl,
3767
+ onChanged
3768
+ }
3769
+ ),
3770
+ showUpdate && /* @__PURE__ */ jsx(
3771
+ RunUpdateDialog,
3772
+ {
3773
+ runId: run.id,
3774
+ handlers: updates,
3775
+ open: updateOpen,
3776
+ onOpenChange: setUpdateOpen,
3777
+ onDone: onChanged
3778
+ }
3779
+ ),
3780
+ showReset && /* @__PURE__ */ jsx(
3781
+ ResetRunDialog,
3782
+ {
3783
+ runId: run.id,
3784
+ point: resetPoint,
3785
+ onOpenChange: (next) => !next && onResetPointChange(null),
3786
+ onReset: onOpenRun
3787
+ }
3788
+ )
3789
+ ] });
3790
+ }
3791
+ function describeCodecError(tr, err) {
3792
+ if (!(err instanceof CodecServerError))
3793
+ return tr(
3794
+ "fluidgrids.runControl.codec.errors.failed",
3795
+ "The payload could not be decoded."
3796
+ );
3797
+ switch (err.code) {
3798
+ case "invalidUrl":
3799
+ return tr(
3800
+ "fluidgrids.runControl.codec.errors.invalidUrl",
3801
+ "The codec server URL must be https (or http to localhost)."
3802
+ );
3803
+ case "unreachable":
3804
+ return tr(
3805
+ "fluidgrids.runControl.codec.errors.unreachable",
3806
+ "The codec server could not be reached: {{detail}}",
3807
+ { detail: err.params.detail ?? "" }
3808
+ );
3809
+ case "http":
3810
+ return tr(
3811
+ "fluidgrids.runControl.codec.errors.http",
3812
+ "The codec server answered HTTP {{status}}.",
3813
+ { status: err.params.status ?? 0 }
3814
+ );
3815
+ case "notPayloadList":
3816
+ return tr(
3817
+ "fluidgrids.runControl.codec.errors.notPayloadList",
3818
+ "The codec server's answer is not a list of payloads."
3819
+ );
3820
+ case "notJson":
3821
+ return tr(
3822
+ "fluidgrids.runControl.codec.errors.notJson",
3823
+ "The codec server returned a payload that is not JSON."
3824
+ );
3825
+ }
3826
+ }
3827
+ var MAX_DEPTH = 32;
3828
+ function formatBytes(bytes) {
3829
+ if (bytes < 1024) return `${bytes} B`;
3830
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
3831
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
3832
+ }
3833
+ function PayloadView({
3834
+ value,
3835
+ codecServer,
3836
+ fetchImpl
3837
+ }) {
3838
+ if (!containsEncodedPayload(value)) {
3839
+ return /* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded bg-background-secondary p-2 font-mono text-text-primary", children: JSON.stringify(value, null, 2) });
3840
+ }
3841
+ return /* @__PURE__ */ jsx(
3842
+ "div",
3843
+ {
3844
+ className: "mt-1 max-h-64 overflow-auto rounded bg-background-secondary p-2 font-mono text-text-primary",
3845
+ "data-testid": "payload-view",
3846
+ children: /* @__PURE__ */ jsx(
3847
+ PayloadNode,
3848
+ {
3849
+ value,
3850
+ codecServer,
3851
+ fetchImpl,
3852
+ depth: 0
3853
+ }
3854
+ )
3855
+ }
3856
+ );
3857
+ }
3858
+ function PayloadNode({
3859
+ value,
3860
+ codecServer,
3861
+ fetchImpl,
3862
+ depth
3863
+ }) {
3864
+ if (isEncodedPayload(value))
3865
+ return /* @__PURE__ */ jsx(
3866
+ EncodedPayloadChip,
3867
+ {
3868
+ payload: value,
3869
+ codecServer,
3870
+ fetchImpl
3871
+ }
3872
+ );
3873
+ if (depth >= MAX_DEPTH || !containsEncodedPayload(value))
3874
+ return /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: JSON.stringify(value) });
3875
+ const entries = Array.isArray(value) ? value.map((item, index) => [String(index), item]) : typeof value === "object" && value !== null ? Object.entries(value) : [];
3876
+ const open = Array.isArray(value) ? "[" : "{";
3877
+ const close = Array.isArray(value) ? "]" : "}";
3878
+ return /* @__PURE__ */ jsxs("span", { children: [
3879
+ open,
3880
+ /* @__PURE__ */ jsx("span", { className: "block pl-4", children: entries.map(([key, item]) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
3881
+ !Array.isArray(value) && /* @__PURE__ */ jsxs("span", { className: "text-text-secondary", children: [
3882
+ JSON.stringify(key),
3883
+ ":",
3884
+ " "
3885
+ ] }),
3886
+ /* @__PURE__ */ jsx(
3887
+ PayloadNode,
3888
+ {
3889
+ value: item,
3890
+ codecServer,
3891
+ fetchImpl,
3892
+ depth: depth + 1
3893
+ }
3894
+ )
3895
+ ] }, key)) }),
3896
+ close
3897
+ ] });
3898
+ }
3899
+ function EncodedPayloadChip({
3900
+ payload,
3901
+ codecServer,
3902
+ fetchImpl
3903
+ }) {
3904
+ const tr = useTr();
3905
+ const [decoded, setDecoded] = useState(null);
3906
+ const [busy, setBusy] = useState(false);
3907
+ const [error, setError] = useState(null);
3908
+ if (decoded && !isEncodedPayload(decoded.value)) {
3909
+ return /* @__PURE__ */ jsxs("span", { className: "inline-block align-top", "data-testid": "payload-decoded", children: [
3910
+ /* @__PURE__ */ jsx("span", { className: "mr-1 rounded border border-border-secondary px-1 text-[10px] uppercase text-text-secondary", children: tr("fluidgrids.runControl.payload.decoded", "decoded") }),
3911
+ /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: JSON.stringify(decoded.value, null, 2) })
3912
+ ] });
3913
+ }
3914
+ const decode = async () => {
3915
+ if (!codecServer) return;
3916
+ setBusy(true);
3917
+ setError(null);
3918
+ try {
3919
+ const [value] = await decodePayloads(codecServer, [payload], fetchImpl);
3920
+ setDecoded({ value });
3921
+ if (isEncodedPayload(value))
3922
+ setError(
3923
+ tr(
3924
+ "fluidgrids.runControl.payload.stillEncoded",
3925
+ "The codec server could not decode this payload."
3926
+ )
3927
+ );
3928
+ } catch (err) {
3929
+ setError(describeCodecError(tr, err));
3930
+ } finally {
3931
+ setBusy(false);
3932
+ }
3933
+ };
3934
+ return /* @__PURE__ */ jsxs(
3935
+ "span",
3936
+ {
3937
+ className: "inline-flex flex-wrap items-center gap-2 align-middle",
3938
+ "data-testid": "payload-encoded",
3939
+ children: [
3940
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-border-secondary bg-background-tertiary px-1.5 py-0.5 text-xs text-text-secondary", children: [
3941
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:lock-outline", width: 12 }),
3942
+ tr("fluidgrids.runControl.payload.encoded", "Encoded payload"),
3943
+ " \xB7",
3944
+ " ",
3945
+ payload.encoding,
3946
+ " \xB7 ",
3947
+ formatBytes(encodedPayloadBytes(payload))
3948
+ ] }),
3949
+ codecServer && /* @__PURE__ */ jsx(
3950
+ Button,
3951
+ {
3952
+ size: "sm",
3953
+ variant: "outline",
3954
+ className: "h-6 px-2 text-xs",
3955
+ onClick: () => void decode(),
3956
+ disabled: busy,
3957
+ "data-testid": "payload-decode",
3958
+ children: busy ? tr("fluidgrids.runControl.payload.decoding", "Decoding\u2026") : tr("fluidgrids.runControl.payload.decode", "Decode")
3959
+ }
3960
+ ),
3961
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "payload-decode-error" })
3962
+ ]
3963
+ }
3964
+ );
3965
+ }
3966
+ function RunArchivedNotice({
3967
+ runId,
3968
+ enabled
3969
+ }) {
3970
+ const tr = useTr();
3971
+ const archive = useResource(
3972
+ `run-archive:${runId}`,
3973
+ enabled,
3974
+ () => fetchRunHistoryArchive(runId)
3975
+ );
3976
+ if (!archive.data?.archived) return null;
3977
+ return /* @__PURE__ */ jsxs(
3978
+ "div",
3979
+ {
3980
+ role: "status",
3981
+ className: "flex items-start gap-2 rounded-md border border-border-secondary bg-background-tertiary p-3 text-sm text-text-primary",
3982
+ "data-testid": "run-archived",
3983
+ children: [
3984
+ /* @__PURE__ */ jsx(
3985
+ Icon,
3986
+ {
3987
+ icon: "mdi:archive-outline",
3988
+ width: 18,
3989
+ className: "mt-0.5 shrink-0 text-text-secondary"
3990
+ }
3991
+ ),
3992
+ /* @__PURE__ */ jsx("span", { children: tr(
3993
+ "fluidgrids.runControl.archive.archived",
3994
+ "This run's history was archived and then purged by retention. It is in {{ref}}.",
3995
+ {
3996
+ ref: archive.data.archiveRef ?? tr(
3997
+ "fluidgrids.runControl.archive.unknownRef",
3998
+ "the workspace's archive bucket"
3999
+ )
4000
+ }
4001
+ ) })
4002
+ ]
4003
+ }
4004
+ );
4005
+ }
4006
+ function useCodecServer(enabled) {
4007
+ const settings = useResource(
4008
+ "codec-server",
4009
+ enabled,
4010
+ fetchCodecServerSettings
4011
+ );
4012
+ return useMemo(() => {
4013
+ const url = settings.data?.codecServerUrl;
4014
+ if (!url || !checkCodecServerUrl(url).ok) return null;
4015
+ return {
4016
+ url,
4017
+ includeCredentials: settings.data?.codecServerIncludeCredentials === true
4018
+ };
4019
+ }, [settings.data]);
4020
+ }
1984
4021
  function formatTime(value) {
1985
4022
  if (!value) return null;
1986
4023
  const date = new Date(value);
@@ -2904,6 +4941,9 @@ function ExecutionDetailPage() {
2904
4941
  );
2905
4942
  const durableEnabled = useDurableFunctionNodesEnabled();
2906
4943
  const parityEnabled = useDurableParityEnabled();
4944
+ const phaseC = usePhaseCFlags();
4945
+ const phaseCEnabled = phaseC.runControl || phaseC.searchAttributes || phaseC.selfHostedWorkers;
4946
+ const [resetPoint, setResetPoint] = useState(null);
2907
4947
  const [showSignalDialog, setShowSignalDialog] = useState(false);
2908
4948
  const [showOverrideDialog, setShowOverrideDialog] = useState(false);
2909
4949
  const [minLogLevel, setMinLogLevel] = useState(null);
@@ -3018,10 +5058,20 @@ function ExecutionDetailPage() {
3018
5058
  ),
3019
5059
  [execution?.nodeStates, execution?.triggerNodeId]
3020
5060
  );
5061
+ const activityControls = isDurableRun && phaseC.runControlDurable;
5062
+ const runPending = useRunPending(runId, {
5063
+ enabled: (parityEnabled || activityControls) && runCanChange,
5064
+ live: isActive
5065
+ });
5066
+ const pendingActivities = runPending.pending?.activities ?? [];
5067
+ const activityControlsShown = activityControls && runCanChange && pendingActivities.length > 0;
3021
5068
  const runHistory = useRunHistory(runId, {
3022
- enabled: isDurableRun && activeTab === "history",
5069
+ enabled: isDurableRun && (activeTab === "history" || activityControlsShown),
3023
5070
  live: isActive
3024
5071
  });
5072
+ const codecServer = useCodecServer(
5073
+ isDurableRun && phaseC.selfHostedWorkers && activeTab === "history"
5074
+ );
3025
5075
  const determinism = useDeterminismStatus(runId, {
3026
5076
  enabled: isDurableRun && runCanChange,
3027
5077
  live: isActive
@@ -3035,10 +5085,6 @@ function ExecutionDetailPage() {
3035
5085
  error: runMetadata.error,
3036
5086
  refetch: runMetadata.refetch
3037
5087
  } : stackTrace;
3038
- const runPending = useRunPending(runId, {
3039
- enabled: parityEnabled && runCanChange,
3040
- live: isActive
3041
- });
3042
5088
  const parityRun = parityEnabled && execution && parityFields.fields?.id === execution.id ? parityFields.fields : null;
3043
5089
  const signalTargets = useMemo(
3044
5090
  () => durableEnabled && execution ? runSignalTargets(execution) : { targets: [], openNames: false },
@@ -3716,6 +5762,32 @@ function ExecutionDetailPage() {
3716
5762
  onViewChildRun: handleViewChildRun
3717
5763
  }
3718
5764
  ),
5765
+ phaseCEnabled && /* @__PURE__ */ jsx(
5766
+ RunControlSection,
5767
+ {
5768
+ run: {
5769
+ id: execution.id,
5770
+ status: execution.status,
5771
+ executionKind: isDurableRun ? "DURABLE_FN" : "GRAPH",
5772
+ workflowFn: execution.workflowFn,
5773
+ taskQueue: execution.taskQueue,
5774
+ definitionJson: execution.workflow?.definitionJson
5775
+ },
5776
+ flags: phaseC,
5777
+ canControl: canControlRuns,
5778
+ activities: activityControlsShown ? pendingActivities : [],
5779
+ workflowTaskWaiting: runPending.pending?.workflowTask?.state === "SCHEDULED",
5780
+ historyEvents: activityControlsShown ? runHistory.events : [],
5781
+ signalNames: signalTargets.targets.map((target) => target.name),
5782
+ onChanged: () => {
5783
+ void refetch();
5784
+ void runPending.refetch();
5785
+ },
5786
+ onOpenRun: (newRunId) => navigate(`../runs/${newRunId}`),
5787
+ resetPoint,
5788
+ onResetPointChange: setResetPoint
5789
+ }
5790
+ ),
3719
5791
  /* @__PURE__ */ jsx("div", { className: "border-b border-border-secondary mb-6", children: /* @__PURE__ */ jsx("nav", { className: "flex gap-4 -mb-px overflow-x-auto", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
3720
5792
  "button",
3721
5793
  {
@@ -3756,6 +5828,7 @@ function ExecutionDetailPage() {
3756
5828
  }
3757
5829
  ) }),
3758
5830
  activeTab === "history" && isDurableRun && /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 sm:p-6", children: [
5831
+ phaseC.runArchival && /* @__PURE__ */ jsx(RunArchivedNotice, { runId: execution.id, enabled: true }),
3759
5832
  runCanChange && /* @__PURE__ */ jsx(
3760
5833
  WaitingAtPanel,
3761
5834
  {
@@ -3778,7 +5851,24 @@ function ExecutionDetailPage() {
3778
5851
  historyLength: runHistory.historyLength,
3779
5852
  onRefresh: () => void runHistory.refetch(),
3780
5853
  onViewChildRun: handleViewChildRun,
3781
- showBenign: parityEnabled
5854
+ showBenign: parityEnabled,
5855
+ renderPayload: phaseC.selfHostedWorkers ? (event) => /* @__PURE__ */ jsx(
5856
+ PayloadView,
5857
+ {
5858
+ value: event.payload,
5859
+ codecServer
5860
+ }
5861
+ ) : void 0,
5862
+ eventActions: phaseC.runControlDurable && canControlRuns ? (event) => /* @__PURE__ */ jsx(
5863
+ "button",
5864
+ {
5865
+ type: "button",
5866
+ className: "text-action-primary-text hover:underline",
5867
+ onClick: () => setResetPoint({ kind: "seq", seq: event.seq }),
5868
+ "data-testid": `history-reset-here-${event.seq}`,
5869
+ children: tr("fluidgrids.runControl.reset.here", "Reset here")
5870
+ }
5871
+ ) : void 0
3782
5872
  }
3783
5873
  )
3784
5874
  ] }),
@@ -4009,4 +6099,4 @@ function ExecutionDetailPage() {
4009
6099
  );
4010
6100
  }
4011
6101
 
4012
- export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests };
6102
+ export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, createTaskQueue, createWorkerToken, deleteTaskQueue, deleteWorkerToken, fetchTaskQueueStats, fetchTaskQueues, fetchWorkerTokens, fetchWorkflowWorkers, rotateWorkerToken, setWorkerTokenConnectionAllowlist, updateTaskQueue, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests };