@burdenoff/fluidgrids-fe-sdk 2026.1101.1 → 2026.1101.3

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 (51) hide show
  1. package/dist/{ExecutionDetailPage-4LPCNWM3.js → ExecutionDetailPage-Q77U6YJO.js} +11 -8
  2. package/dist/{ExecutionsListPage-HNPUKYD7.js → ExecutionsListPage-X4H4FVLN.js} +6 -5
  3. package/dist/{WorkflowBuilder-QFSCYCUC.js → WorkflowBuilder-RJPRUMQR.js} +11 -8
  4. package/dist/{WorkflowsList-RV4BRJO7.js → WorkflowsList-R2JJK7BL.js} +7 -6
  5. package/dist/agentTools-MISTH7JI.js +349 -0
  6. package/dist/{chunk-77HIU3CX.js → chunk-26HGYPBC.js} +1 -1
  7. package/dist/{chunk-76UUEMTN.js → chunk-37VUN74D.js} +4 -4
  8. package/dist/{chunk-QPZCJKII.js → chunk-3ERT6FQK.js} +3 -3
  9. package/dist/chunk-472HJ3S3.js +3929 -0
  10. package/dist/{chunk-WNZV4SLU.js → chunk-4QHCXTHW.js} +4 -4
  11. package/dist/chunk-AASHQI77.js +126 -0
  12. package/dist/chunk-CGXQVZGY.js +260 -0
  13. package/dist/chunk-CRDG2SQQ.js +185 -0
  14. package/dist/chunk-DQ2ZCXYX.js +21875 -0
  15. package/dist/chunk-F7DD7BOB.js +1164 -0
  16. package/dist/chunk-GTEBQQ7J.js +24 -0
  17. package/dist/{chunk-ALFMJSD6.js → chunk-H7XPXONT.js} +1 -1
  18. package/dist/{chunk-UO5DUKYN.js → chunk-HKKSXOFO.js} +8 -7
  19. package/dist/chunk-IA2ZCHNQ.js +1336 -0
  20. package/dist/{chunk-3M36XWAH.js → chunk-IGRIN7NL.js} +8 -7
  21. package/dist/chunk-JDLQ6QIB.js +283 -0
  22. package/dist/{chunk-AD7BICOT.js → chunk-JKBCLBHY.js} +1 -1
  23. package/dist/{chunk-6JX5UPDW.js → chunk-JNZCXFLD.js} +1 -1
  24. package/dist/{chunk-SEFOT2LD.js → chunk-LWLHPVIA.js} +1 -1
  25. package/dist/{chunk-JWROEOG5.js → chunk-LYTR6IQO.js} +1 -38
  26. package/dist/chunk-MR2B67NW.js +338 -0
  27. package/dist/{chunk-RIHYPPMQ.js → chunk-NLYQ3XDE.js} +1 -1
  28. package/dist/{chunk-6ILIBJFH.js → chunk-NZJWKVER.js} +7904 -7943
  29. package/dist/chunk-OCJTTHTS.js +46 -0
  30. package/dist/chunk-OSQ3OCR3.js +1193 -0
  31. package/dist/{chunk-VJL3DFJT.js → chunk-REO4S5MY.js} +2 -2
  32. package/dist/{chunk-XKQMCPSB.js → chunk-T2UUYYVJ.js} +4 -4
  33. package/dist/{chunk-BXR6BWXM.js → chunk-W7MTSATB.js} +5 -5
  34. package/dist/{chunk-A47PSD7A.js → chunk-WF6GMBR4.js} +7 -4
  35. package/dist/chunk-WYCB6OQL.js +19709 -0
  36. package/dist/{chunk-YOLPHVOE.js → chunk-YKFR7BZJ.js} +1 -1
  37. package/dist/{chunk-26NENGXA.js → chunk-YSXNJGCY.js} +4 -4
  38. package/dist/code-editor-field-JKCUR6BI.js +3 -0
  39. package/dist/executions.js +12 -9
  40. package/dist/history.js +4 -3
  41. package/dist/index.js +41 -32
  42. package/dist/node-detail-view-PCYFQX62.js +2034 -0
  43. package/dist/observability.js +5 -4
  44. package/dist/quick-config-panel-BRZZHOSI.js +402 -0
  45. package/dist/settings.js +6 -5
  46. package/dist/templates.js +10 -7
  47. package/dist/workflow-builder.js +12 -9
  48. package/dist/workflows-list.js +7 -6
  49. package/package.json +4 -1
  50. package/dist/chunk-NMEI7TQW.js +0 -2628
  51. package/dist/chunk-S7XV65DB.js +0 -50350
@@ -0,0 +1,1193 @@
1
+ import { throwIfResultError, describeOperationError, signalPayloadCard, components_default, payloadFromCardValues, parseRawSignalPayload } from './chunk-NZJWKVER.js';
2
+ import { TerminateWorkflowRunDocument, WorkflowRunHistoryDocument, RunEventAppendedDocument, DurableRunFieldsDocument, WorkflowRunDeterminismStatusDocument, WorkflowRunStackTraceDocument, SignalWorkflowRunDocument } from './chunk-IHEV5HTD.js';
3
+ import { useTr } from './chunk-UULVIT25.js';
4
+ import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
5
+ import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
6
+ import { Icon } from '@iconify/react';
7
+ import { Button } from '@burdenoff/fe-libs/button';
8
+ import { cn } from '@burdenoff/fe-libs/utils';
9
+ import { jsxs, jsx } from 'react/jsx-runtime';
10
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Textarea, DialogFooter, Input } from '@burdenoff/fe-libs/ui';
11
+
12
+ // src/modules/executions/utils/run-history.ts
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ function str(value) {
17
+ return typeof value === "string" && value.trim() ? value : void 0;
18
+ }
19
+ function num(value) {
20
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
21
+ }
22
+ function payloadOf(event) {
23
+ return isRecord(event.payload) ? event.payload : {};
24
+ }
25
+ function mergeHistoryEvents(current, incoming) {
26
+ if (incoming.length === 0) return current;
27
+ const seen = new Set(current.map((event) => event.seq));
28
+ const fresh = incoming.filter((event) => {
29
+ if (seen.has(event.seq)) return false;
30
+ seen.add(event.seq);
31
+ return true;
32
+ });
33
+ if (fresh.length === 0) return current;
34
+ return [...current, ...fresh].sort((a, b) => a.seq - b.seq);
35
+ }
36
+ function lastSeqOf(events) {
37
+ return events.length ? events[events.length - 1].seq : 0;
38
+ }
39
+ function formatDurationMs(ms) {
40
+ if (ms < 1e3) return `${Math.max(0, Math.round(ms))}ms`;
41
+ const seconds = Math.round(ms / 1e3);
42
+ if (seconds < 60) return `${seconds}s`;
43
+ const minutes = Math.floor(seconds / 60);
44
+ if (minutes < 60) {
45
+ const rest2 = seconds % 60;
46
+ return rest2 ? `${minutes}m ${rest2}s` : `${minutes}m`;
47
+ }
48
+ const hours = Math.floor(minutes / 60);
49
+ if (hours < 24) {
50
+ const rest2 = minutes % 60;
51
+ return rest2 ? `${hours}h ${rest2}m` : `${hours}h`;
52
+ }
53
+ const days = Math.floor(hours / 24);
54
+ const rest = hours % 24;
55
+ return rest ? `${days}d ${rest}h` : `${days}d`;
56
+ }
57
+ function failureOf(value) {
58
+ if (!isRecord(value)) return void 0;
59
+ const message = str(value.message);
60
+ if (!message) return void 0;
61
+ return {
62
+ message,
63
+ errorType: str(value.errorType) ?? str(value.type),
64
+ ...value.category === "BENIGN" ? { benign: true } : {}
65
+ };
66
+ }
67
+ function groupKeyOf(event) {
68
+ const payload = payloadOf(event);
69
+ const type = event.type;
70
+ if (type.startsWith("WORKFLOW_TASK_")) {
71
+ return ["task", str(payload.taskId) ?? str(event.taskId) ?? `${event.seq}`];
72
+ }
73
+ if (type.startsWith("ACTIVITY_")) {
74
+ return ["activity", str(payload.activityId) ?? `${event.seq}`];
75
+ }
76
+ if (type.startsWith("LOCAL_ACTIVITY_")) {
77
+ return ["activity", str(payload.localActivityId) ?? `${event.seq}`];
78
+ }
79
+ if (type.startsWith("TIMER_")) {
80
+ return ["timer", str(payload.timerId) ?? `${event.seq}`];
81
+ }
82
+ if (type.startsWith("CHILD_RUN_")) {
83
+ return ["child", str(payload.childId) ?? `${event.seq}`];
84
+ }
85
+ if (type.startsWith("EXTERNAL_RUN_")) {
86
+ return ["external", str(payload.externalId) ?? `${event.seq}`];
87
+ }
88
+ if (type.startsWith("UPDATE_")) {
89
+ return ["update", str(payload.updateId) ?? `${event.seq}`];
90
+ }
91
+ if (type === "SIGNAL_RECEIVED") return ["signal", `${event.seq}`];
92
+ if (type === "MARKER_RECORDED") return ["marker", `${event.seq}`];
93
+ if (type === "SIDE_EFFECT_RECORDED") return ["sideEffect", `${event.seq}`];
94
+ if (type === "CONTEXT_UPSERTED") return ["context", `${event.seq}`];
95
+ if (type.startsWith("WORKFLOW_")) return ["workflow", `${event.seq}`];
96
+ return ["other", `${event.seq}`];
97
+ }
98
+ var ACTIVITY_STATUS = {
99
+ ACTIVITY_SCHEDULED: "scheduled",
100
+ ACTIVITY_STARTED: "running",
101
+ ACTIVITY_COMPLETED: "completed",
102
+ ACTIVITY_FAILED: "failed",
103
+ ACTIVITY_TIMED_OUT: "timedOut",
104
+ ACTIVITY_CANCEL_REQUESTED: "cancelRequested",
105
+ ACTIVITY_CANCELLED: "cancelled",
106
+ LOCAL_ACTIVITY_COMPLETED: "completed",
107
+ LOCAL_ACTIVITY_FAILED: "failed"
108
+ };
109
+ var TASK_STATUS = {
110
+ WORKFLOW_TASK_SCHEDULED: "scheduled",
111
+ WORKFLOW_TASK_STARTED: "running",
112
+ WORKFLOW_TASK_COMPLETED: "completed",
113
+ WORKFLOW_TASK_FAILED: "failed",
114
+ WORKFLOW_TASK_TIMED_OUT: "timedOut"
115
+ };
116
+ var TIMER_STATUS = {
117
+ TIMER_STARTED: "running",
118
+ TIMER_FIRED: "fired",
119
+ TIMER_CANCELLED: "cancelled"
120
+ };
121
+ var CHILD_STATUS = {
122
+ CHILD_RUN_INITIATED: "scheduled",
123
+ CHILD_RUN_STARTED: "running",
124
+ CHILD_RUN_COMPLETED: "completed",
125
+ CHILD_RUN_FAILED: "failed",
126
+ CHILD_RUN_CANCELLED: "cancelled",
127
+ CHILD_RUN_TERMINATED: "terminated",
128
+ CHILD_RUN_TIMED_OUT: "timedOut"
129
+ };
130
+ var WORKFLOW_STATUS = {
131
+ WORKFLOW_STARTED: "running",
132
+ WORKFLOW_COMPLETED: "completed",
133
+ WORKFLOW_FAILED: "failed",
134
+ WORKFLOW_CANCELLED: "cancelled",
135
+ WORKFLOW_CANCEL_REQUESTED: "cancelRequested",
136
+ WORKFLOW_TERMINATED: "terminated",
137
+ WORKFLOW_TIMED_OUT: "timedOut",
138
+ WORKFLOW_CONTINUED_AS_NEW: "continued"
139
+ };
140
+ var EXTERNAL_STATUS = {
141
+ EXTERNAL_RUN_CANCEL_REQUESTED: "cancelRequested",
142
+ EXTERNAL_RUN_CANCEL_RESOLVED: "completed",
143
+ EXTERNAL_RUN_SIGNALED: "completed"
144
+ };
145
+ var UPDATE_STATUS = {
146
+ UPDATE_ADMITTED: "scheduled",
147
+ UPDATE_ACCEPTED: "running",
148
+ UPDATE_COMPLETED: "completed"
149
+ };
150
+ var FINAL_STATUSES = /* @__PURE__ */ new Set([
151
+ "completed",
152
+ "failed",
153
+ "timedOut",
154
+ "cancelled",
155
+ "terminated",
156
+ "continued",
157
+ "fired",
158
+ "received",
159
+ "recorded"
160
+ ]);
161
+ function statusFor(kind, type) {
162
+ switch (kind) {
163
+ case "activity":
164
+ return ACTIVITY_STATUS[type];
165
+ case "task":
166
+ return TASK_STATUS[type];
167
+ case "timer":
168
+ return TIMER_STATUS[type];
169
+ case "child":
170
+ return CHILD_STATUS[type];
171
+ case "workflow":
172
+ return WORKFLOW_STATUS[type];
173
+ case "external":
174
+ return EXTERNAL_STATUS[type];
175
+ case "update":
176
+ return UPDATE_STATUS[type];
177
+ case "signal":
178
+ return "received";
179
+ default:
180
+ return "recorded";
181
+ }
182
+ }
183
+ var WORKFLOW_LABELS = {
184
+ WORKFLOW_STARTED: "Workflow started",
185
+ WORKFLOW_COMPLETED: "Workflow completed",
186
+ WORKFLOW_FAILED: "Workflow failed",
187
+ WORKFLOW_CANCELLED: "Workflow cancelled",
188
+ WORKFLOW_CANCEL_REQUESTED: "Cancel requested",
189
+ WORKFLOW_TERMINATED: "Workflow terminated",
190
+ WORKFLOW_TIMED_OUT: "Workflow timed out",
191
+ WORKFLOW_CONTINUED_AS_NEW: "Continued as new",
192
+ WORKFLOW_RESET: "Workflow reset"
193
+ };
194
+ function humanizeEventType(type) {
195
+ const words = type.toLowerCase().split("_").filter(Boolean);
196
+ if (!words.length) return type;
197
+ const first = words[0];
198
+ return [first.charAt(0).toUpperCase() + first.slice(1), ...words.slice(1)].join(" ").trim();
199
+ }
200
+ function describe(kind, id, first) {
201
+ const payload = payloadOf(first);
202
+ const summary = str(payload.summary);
203
+ switch (kind) {
204
+ case "activity": {
205
+ const nodeType = str(payload.nodeType);
206
+ const operation = str(payload.operation);
207
+ const target = [nodeType, operation].filter(Boolean).join(" \xB7 ");
208
+ return {
209
+ label: summary ?? (target || `Activity ${id}`),
210
+ detail: summary && target ? target : id
211
+ };
212
+ }
213
+ case "timer": {
214
+ const duration = num(payload.durationMs);
215
+ const span = duration !== void 0 ? formatDurationMs(duration) : "";
216
+ return {
217
+ label: summary ?? (span ? `Timer ${span}` : `Timer ${id}`),
218
+ detail: summary && span ? span : id
219
+ };
220
+ }
221
+ case "child": {
222
+ const target = str(payload.workflowFn) ?? str(payload.workflowKey);
223
+ return {
224
+ label: summary ?? (target ? `Child ${target}` : `Child run ${id}`),
225
+ detail: id
226
+ };
227
+ }
228
+ case "signal":
229
+ return { label: `Signal ${str(payload.name) ?? ""}`.trim() };
230
+ case "task":
231
+ return { label: "Workflow task", detail: id };
232
+ case "external": {
233
+ const name = str(payload.name);
234
+ return {
235
+ label: name ? `Signal ${name} to another run` : first.type === "EXTERNAL_RUN_CANCEL_REQUESTED" ? "Cancel another run" : humanizeEventType(first.type),
236
+ detail: id
237
+ };
238
+ }
239
+ case "marker": {
240
+ const markerId = str(payload.markerId) ?? "";
241
+ return {
242
+ label: markerId.startsWith("patch:") ? `Patch ${markerId.slice("patch:".length)}` : "Marker",
243
+ detail: markerId || void 0
244
+ };
245
+ }
246
+ case "sideEffect":
247
+ return { label: "Side effect", detail: str(payload.sideEffectId) };
248
+ case "context":
249
+ return { label: "Context updated" };
250
+ case "update":
251
+ return { label: `Update ${str(payload.name) ?? id}` };
252
+ case "workflow":
253
+ return {
254
+ label: WORKFLOW_LABELS[first.type] ?? humanizeEventType(first.type)
255
+ };
256
+ default:
257
+ return { label: humanizeEventType(first.type) };
258
+ }
259
+ }
260
+ function groupRunHistory(events) {
261
+ const groups = /* @__PURE__ */ new Map();
262
+ const ordered = [...events].sort((a, b) => a.seq - b.seq);
263
+ for (const event of ordered) {
264
+ const [kind, id] = groupKeyOf(event);
265
+ const key = `${kind}:${id}`;
266
+ const payload = payloadOf(event);
267
+ let group = groups.get(key);
268
+ if (!group) {
269
+ const { label, detail } = describe(kind, id, event);
270
+ group = {
271
+ key,
272
+ kind,
273
+ id,
274
+ label,
275
+ detail,
276
+ status: statusFor(kind, event.type) ?? "recorded",
277
+ events: [],
278
+ firstSeq: event.seq,
279
+ lastSeq: event.seq,
280
+ startedAt: event.createdAt
281
+ };
282
+ groups.set(key, group);
283
+ }
284
+ group.events.push(event);
285
+ group.lastSeq = event.seq;
286
+ const status = statusFor(kind, event.type);
287
+ if (status && !(status === "cancelRequested" && FINAL_STATUSES.has(group.status))) {
288
+ group.status = status;
289
+ }
290
+ if (FINAL_STATUSES.has(group.status)) group.endedAt = event.createdAt;
291
+ const attempt = num(payload.attempt);
292
+ if (attempt !== void 0)
293
+ group.attempt = Math.max(group.attempt ?? 0, attempt);
294
+ const failure = failureOf(payload.failure) ?? (event.type === "WORKFLOW_TASK_FAILED" || event.type === "WORKFLOW_TASK_TIMED_OUT" ? failureOf({ message: payload.message, errorType: payload.cause }) : void 0);
295
+ if (failure) {
296
+ group.failure = {
297
+ ...failure,
298
+ cause: str(payload.cause)
299
+ };
300
+ }
301
+ const childRunId = str(payload.childRunId);
302
+ if (kind === "child" && childRunId) group.childRunId = childRunId;
303
+ }
304
+ return [...groups.values()].sort((a, b) => a.firstSeq - b.firstSeq);
305
+ }
306
+ var KIND_ICONS = {
307
+ workflow: "mdi:flag-outline",
308
+ task: "mdi:cog-sync-outline",
309
+ activity: "mdi:lightning-bolt-outline",
310
+ timer: "mdi:timer-sand",
311
+ child: "mdi:source-branch",
312
+ signal: "mdi:bell-ring-outline",
313
+ external: "mdi:send-outline",
314
+ marker: "mdi:bookmark-outline",
315
+ sideEffect: "mdi:dice-multiple-outline",
316
+ context: "mdi:tag-text-outline",
317
+ update: "mdi:pencil-outline",
318
+ other: "mdi:circle-small"
319
+ };
320
+ var STATUS_CLASS = {
321
+ scheduled: "bg-background-tertiary text-text-secondary border-border-secondary",
322
+ running: "bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)] border-[var(--color-status-info-border)]",
323
+ completed: "bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]",
324
+ fired: "bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]",
325
+ received: "bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]",
326
+ recorded: "bg-background-tertiary text-text-secondary border-border-secondary",
327
+ failed: "bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)] border-[var(--color-status-error-border)]",
328
+ timedOut: "bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)] border-[var(--color-status-error-border)]",
329
+ terminated: "bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)] border-[var(--color-status-error-border)]",
330
+ cancelRequested: "bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]",
331
+ cancelled: "bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]",
332
+ continued: "bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)] border-[var(--color-status-info-border)]"
333
+ };
334
+ var BENIGN_CLASS = "bg-background-tertiary text-text-secondary border-border-secondary";
335
+ var STATUS_LABEL = {
336
+ scheduled: ["fluidgrids.executions.history.status.scheduled", "Scheduled"],
337
+ running: ["fluidgrids.executions.history.status.running", "Running"],
338
+ completed: ["fluidgrids.executions.history.status.completed", "Completed"],
339
+ failed: ["fluidgrids.executions.history.status.failed", "Failed"],
340
+ timedOut: ["fluidgrids.executions.history.status.timedOut", "Timed out"],
341
+ cancelled: ["fluidgrids.executions.history.status.cancelled", "Cancelled"],
342
+ cancelRequested: [
343
+ "fluidgrids.executions.history.status.cancelRequested",
344
+ "Cancel requested"
345
+ ],
346
+ terminated: ["fluidgrids.executions.history.status.terminated", "Terminated"],
347
+ continued: ["fluidgrids.executions.history.status.continued", "Continued"],
348
+ fired: ["fluidgrids.executions.history.status.fired", "Fired"],
349
+ received: ["fluidgrids.executions.history.status.received", "Received"],
350
+ recorded: ["fluidgrids.executions.history.status.recorded", "Recorded"]
351
+ };
352
+ function isHttpsUrl(value) {
353
+ if (!value) return false;
354
+ try {
355
+ return new URL(value).protocol === "https:";
356
+ } catch {
357
+ return false;
358
+ }
359
+ }
360
+ function formatTime(value) {
361
+ const date = new Date(value);
362
+ if (Number.isNaN(date.getTime())) return value;
363
+ return date.toLocaleTimeString("en-US", {
364
+ hour: "2-digit",
365
+ minute: "2-digit",
366
+ second: "2-digit"
367
+ });
368
+ }
369
+ function RunHistoryView({
370
+ events,
371
+ loading,
372
+ error,
373
+ active,
374
+ streaming,
375
+ truncated = false,
376
+ historyLength,
377
+ onRefresh,
378
+ onViewChildRun,
379
+ defaultShowTasks = false,
380
+ compact = false,
381
+ showBenign = false,
382
+ "data-testid": testId = "run-history"
383
+ }) {
384
+ const tr = useTr();
385
+ const [showTasks, setShowTasks] = useState(defaultShowTasks);
386
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
387
+ const groups = useMemo(() => groupRunHistory(events), [events]);
388
+ const visible = useMemo(
389
+ () => showTasks ? groups : groups.filter((g) => g.kind !== "task"),
390
+ [groups, showTasks]
391
+ );
392
+ const toggle = (key) => setExpanded((current) => {
393
+ const next = new Set(current);
394
+ if (next.has(key)) next.delete(key);
395
+ else next.add(key);
396
+ return next;
397
+ });
398
+ return /* @__PURE__ */ jsxs("div", { "data-testid": testId, className: "flex flex-col gap-3", children: [
399
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-sm text-text-secondary", children: [
400
+ /* @__PURE__ */ jsx("span", { "data-testid": `${testId}-count`, children: tr("fluidgrids.executions.history.eventCount", "{{count}} events", {
401
+ count: historyLength ?? events.length
402
+ }) }),
403
+ active && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
404
+ /* @__PURE__ */ jsx(
405
+ Icon,
406
+ {
407
+ icon: streaming ? "mdi:access-point" : "mdi:refresh",
408
+ width: 14
409
+ }
410
+ ),
411
+ streaming ? tr("fluidgrids.executions.history.live", "Live") : tr("fluidgrids.executions.history.polling", "Refreshing")
412
+ ] }),
413
+ /* @__PURE__ */ jsxs("label", { className: "ml-auto flex items-center gap-2", children: [
414
+ /* @__PURE__ */ jsx(
415
+ "input",
416
+ {
417
+ type: "checkbox",
418
+ checked: showTasks,
419
+ onChange: (e) => setShowTasks(e.target.checked),
420
+ className: "h-4 w-4 rounded",
421
+ "data-testid": `${testId}-show-tasks`
422
+ }
423
+ ),
424
+ tr("fluidgrids.executions.history.showTasks", "Show workflow tasks")
425
+ ] }),
426
+ onRefresh && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRefresh, children: [
427
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", className: "mr-1", width: 16 }),
428
+ tr("fluidgrids.executions.history.refresh", "Refresh")
429
+ ] })
430
+ ] }),
431
+ error && /* @__PURE__ */ jsxs(
432
+ "div",
433
+ {
434
+ role: "alert",
435
+ className: "rounded-md border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bg-subtle)] p-3 text-sm text-[var(--color-status-error-text)]",
436
+ children: [
437
+ tr(
438
+ "fluidgrids.executions.history.loadError",
439
+ "Could not load the run history:"
440
+ ),
441
+ " ",
442
+ error.message
443
+ ]
444
+ }
445
+ ),
446
+ truncated && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg-subtle)] p-3 text-sm text-[var(--color-status-warning-text)]", children: tr(
447
+ "fluidgrids.executions.history.truncated",
448
+ "Only the first events are shown. Use fluidgrids history <runId> for the full history."
449
+ ) }),
450
+ loading && events.length === 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-2", "aria-busy": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
451
+ "div",
452
+ {
453
+ className: "h-10 animate-pulse rounded bg-background-tertiary"
454
+ },
455
+ i
456
+ )) }) : visible.length === 0 && !error ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-text-secondary", children: tr("fluidgrids.executions.history.empty", "No events recorded yet.") }) : /* @__PURE__ */ jsx("ol", { className: "flex flex-col divide-y divide-border-secondary rounded-md border border-border-secondary", children: visible.map((group) => /* @__PURE__ */ jsx(
457
+ HistoryRow,
458
+ {
459
+ group,
460
+ expanded: expanded.has(group.key),
461
+ onToggle: () => toggle(group.key),
462
+ onViewChildRun,
463
+ compact,
464
+ benign: showBenign && group.failure?.benign === true,
465
+ statusLabel: tr(...STATUS_LABEL[group.status])
466
+ },
467
+ group.key
468
+ )) })
469
+ ] });
470
+ }
471
+ function HistoryRow({
472
+ group,
473
+ expanded,
474
+ onToggle,
475
+ onViewChildRun,
476
+ compact,
477
+ benign,
478
+ statusLabel
479
+ }) {
480
+ const tr = useTr();
481
+ return /* @__PURE__ */ jsxs(
482
+ "li",
483
+ {
484
+ "data-testid": `history-group-${group.key}`,
485
+ "data-status": group.status,
486
+ "data-benign": benign ? "true" : void 0,
487
+ children: [
488
+ /* @__PURE__ */ jsxs(
489
+ "button",
490
+ {
491
+ type: "button",
492
+ onClick: onToggle,
493
+ "aria-expanded": expanded,
494
+ className: cn(
495
+ "flex w-full items-center gap-3 text-left hover:bg-background-tertiary",
496
+ compact ? "px-2 py-1.5" : "px-3 py-2"
497
+ ),
498
+ children: [
499
+ /* @__PURE__ */ jsx(
500
+ Icon,
501
+ {
502
+ icon: expanded ? "mdi:chevron-down" : "mdi:chevron-right",
503
+ width: 16,
504
+ className: "shrink-0 text-text-secondary"
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsx(
508
+ Icon,
509
+ {
510
+ icon: KIND_ICONS[group.kind],
511
+ width: 16,
512
+ className: "shrink-0 text-text-secondary"
513
+ }
514
+ ),
515
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
516
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium text-text-primary", children: group.label }),
517
+ group.detail && /* @__PURE__ */ jsx("span", { className: "block truncate font-mono text-xs text-text-secondary", children: group.detail })
518
+ ] }),
519
+ group.attempt !== void 0 && group.attempt > 1 && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-text-secondary", children: tr(
520
+ "fluidgrids.executions.history.attempt",
521
+ "attempt {{attempt}}",
522
+ {
523
+ attempt: group.attempt
524
+ }
525
+ ) }),
526
+ /* @__PURE__ */ jsx(
527
+ "span",
528
+ {
529
+ className: cn(
530
+ "shrink-0 rounded border px-1.5 py-0.5 text-xs",
531
+ benign ? BENIGN_CLASS : STATUS_CLASS[group.status]
532
+ ),
533
+ children: benign ? tr("fluidgrids.executions.history.status.benign", "Expected") : statusLabel
534
+ }
535
+ ),
536
+ !compact && /* @__PURE__ */ jsx("span", { className: "w-20 shrink-0 text-right font-mono text-xs text-text-secondary", children: formatTime(group.startedAt) })
537
+ ]
538
+ }
539
+ ),
540
+ group.failure && /* @__PURE__ */ jsxs(
541
+ "p",
542
+ {
543
+ className: cn(
544
+ "px-10 pb-2 text-xs",
545
+ benign ? "text-text-secondary" : "text-[var(--color-status-error-text)]"
546
+ ),
547
+ children: [
548
+ group.failure.errorType ? `${group.failure.errorType}: ` : "",
549
+ group.failure.message
550
+ ]
551
+ }
552
+ ),
553
+ expanded && /* @__PURE__ */ jsxs("div", { className: "space-y-2 bg-background-primary px-10 py-2", children: [
554
+ group.childRunId && onViewChildRun && /* @__PURE__ */ jsxs(
555
+ Button,
556
+ {
557
+ variant: "outline",
558
+ size: "sm",
559
+ onClick: () => onViewChildRun(group.childRunId),
560
+ children: [
561
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:open-in-new", className: "mr-1", width: 14 }),
562
+ tr("fluidgrids.executions.history.openChild", "Open child run")
563
+ ]
564
+ }
565
+ ),
566
+ group.events.map((event) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
567
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 font-mono text-text-secondary", children: [
568
+ /* @__PURE__ */ jsxs("span", { children: [
569
+ "#",
570
+ event.seq
571
+ ] }),
572
+ /* @__PURE__ */ jsx("span", { className: "text-text-primary", children: event.type }),
573
+ /* @__PURE__ */ jsx("span", { className: "ml-auto", children: formatTime(event.createdAt) })
574
+ ] }),
575
+ isHttpsUrl(event.payloadUrl) ? /* @__PURE__ */ jsx(
576
+ "a",
577
+ {
578
+ href: event.payloadUrl ?? void 0,
579
+ target: "_blank",
580
+ rel: "noreferrer noopener",
581
+ className: "text-action-primary-text hover:underline",
582
+ children: tr(
583
+ "fluidgrids.executions.history.offloadedPayload",
584
+ "Download payload"
585
+ )
586
+ }
587
+ ) : event.payload && Object.keys(event.payload).length > 0 ? /* @__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(event.payload, null, 2) }) : null
588
+ ] }, event.seq))
589
+ ] })
590
+ ]
591
+ }
592
+ );
593
+ }
594
+ async function fetchDurableRunFields(runId) {
595
+ const result = await getFluidGridsClient().query({
596
+ query: DurableRunFieldsDocument,
597
+ variables: { id: runId },
598
+ fetchPolicy: "network-only"
599
+ });
600
+ throwIfResultError(result);
601
+ return result.data?.workflowRun ?? null;
602
+ }
603
+ function useDurableRunFields(runId, enabled) {
604
+ const [fields, setFields] = useState(null);
605
+ const request = useRef(0);
606
+ const isGraphRun = useRef(false);
607
+ const read = useCallback(async () => {
608
+ if (!runId || !enabled) return;
609
+ const id = ++request.current;
610
+ try {
611
+ const next = await fetchDurableRunFields(runId);
612
+ if (id !== request.current) return;
613
+ isGraphRun.current = next?.executionKind === "GRAPH" /* Graph */;
614
+ setFields(next);
615
+ } catch {
616
+ if (id !== request.current) return;
617
+ }
618
+ }, [runId, enabled]);
619
+ useEffect(() => {
620
+ request.current += 1;
621
+ isGraphRun.current = false;
622
+ setFields(null);
623
+ void read();
624
+ }, [read]);
625
+ const refetch = useCallback(async () => {
626
+ if (isGraphRun.current) return;
627
+ await read();
628
+ }, [read]);
629
+ return { fields: enabled ? fields : null, refetch };
630
+ }
631
+ var DETERMINISM_POLL_INTERVAL_MS = 1e4;
632
+ async function fetchDeterminismStatus(runId) {
633
+ const result = await getFluidGridsClient().query({
634
+ query: WorkflowRunDeterminismStatusDocument,
635
+ variables: { runId },
636
+ fetchPolicy: "network-only"
637
+ });
638
+ throwIfResultError(result);
639
+ return result.data?.workflowRunDeterminismStatus ?? null;
640
+ }
641
+ async function fetchRunStackTrace(runId) {
642
+ const result = await getFluidGridsClient().query({
643
+ query: WorkflowRunStackTraceDocument,
644
+ variables: { runId, enhanced: false },
645
+ fetchPolicy: "network-only"
646
+ });
647
+ throwIfResultError(result);
648
+ return result.data?.workflowRunStackTrace.trace ?? null;
649
+ }
650
+ function newSignalIdempotencyKey() {
651
+ return crypto.randomUUID();
652
+ }
653
+ async function sendRunSignal(input) {
654
+ const result = await getFluidGridsClient().mutate({
655
+ mutation: SignalWorkflowRunDocument,
656
+ variables: {
657
+ runId: input.runId,
658
+ name: input.name,
659
+ // The JSON scalar carries any value; the generated type is the object
660
+ // form only, and the service validates the payload against the schema.
661
+ payload: input.payload,
662
+ idempotencyKey: input.idempotencyKey
663
+ }
664
+ });
665
+ throwIfResultError(result);
666
+ const data = result.data?.signalWorkflowRun;
667
+ if (!data) throw new Error("The signal returned no result");
668
+ return data;
669
+ }
670
+ async function terminateDurableRun(runId, reason) {
671
+ const result = await getFluidGridsClient().mutate({
672
+ mutation: TerminateWorkflowRunDocument,
673
+ variables: { runId, reason }
674
+ });
675
+ throwIfResultError(result);
676
+ }
677
+ function useDeterminismStatus(runId, { enabled, live }) {
678
+ const [status, setStatus] = useState(null);
679
+ const [error, setError] = useState(null);
680
+ const request = useRef(0);
681
+ const refetch = useCallback(async () => {
682
+ if (!runId || !enabled) return;
683
+ const id = ++request.current;
684
+ try {
685
+ const next = await fetchDeterminismStatus(runId);
686
+ if (id !== request.current) return;
687
+ setStatus(next);
688
+ setError(null);
689
+ } catch (err) {
690
+ if (id !== request.current) return;
691
+ setError(describeOperationError(err));
692
+ }
693
+ }, [runId, enabled]);
694
+ useEffect(() => {
695
+ request.current += 1;
696
+ setStatus(null);
697
+ setError(null);
698
+ void refetch();
699
+ }, [refetch]);
700
+ useEffect(() => {
701
+ if (!live || !enabled || !runId) return;
702
+ const timer = setInterval(() => {
703
+ void refetch();
704
+ }, DETERMINISM_POLL_INTERVAL_MS);
705
+ return () => clearInterval(timer);
706
+ }, [live, enabled, runId, refetch]);
707
+ return { status, error, refetch };
708
+ }
709
+ function useRunStackTrace(runId, enabled) {
710
+ const [trace, setTrace] = useState(null);
711
+ const [loading, setLoading] = useState(false);
712
+ const [error, setError] = useState(null);
713
+ const request = useRef(0);
714
+ const refetch = useCallback(async () => {
715
+ if (!runId || !enabled) return;
716
+ const id = ++request.current;
717
+ setLoading(true);
718
+ try {
719
+ const next = await fetchRunStackTrace(runId);
720
+ if (id !== request.current) return;
721
+ setTrace(next);
722
+ setError(null);
723
+ } catch (err) {
724
+ if (id !== request.current) return;
725
+ setError(describeOperationError(err));
726
+ } finally {
727
+ if (id === request.current) setLoading(false);
728
+ }
729
+ }, [runId, enabled]);
730
+ useEffect(() => {
731
+ request.current += 1;
732
+ setTrace(null);
733
+ setError(null);
734
+ setLoading(false);
735
+ void refetch();
736
+ }, [refetch]);
737
+ return { trace, loading, error, refetch };
738
+ }
739
+ function useOpenSnapshot(open, value) {
740
+ const [snapshot, setSnapshot] = useState(
741
+ () => open ? { value } : null
742
+ );
743
+ if (open && snapshot === null) {
744
+ setSnapshot({ value });
745
+ return value;
746
+ }
747
+ if (!open && snapshot !== null) {
748
+ setSnapshot(null);
749
+ }
750
+ return snapshot === null ? value : snapshot.value;
751
+ }
752
+ var CUSTOM = "__custom__";
753
+ function CustomSignalNameInput({
754
+ value,
755
+ onChange
756
+ }) {
757
+ const tr = useTr();
758
+ return /* @__PURE__ */ jsx(
759
+ Input,
760
+ {
761
+ value,
762
+ onChange: (e) => onChange(e.target.value),
763
+ placeholder: tr(
764
+ "fluidgrids.executions.signal.namePlaceholder",
765
+ "signal-name"
766
+ ),
767
+ "aria-label": tr(
768
+ "fluidgrids.executions.signal.customNameLabel",
769
+ "Signal name"
770
+ ),
771
+ "data-testid": "signal-custom-name"
772
+ }
773
+ );
774
+ }
775
+ function PayloadModeToggle({
776
+ mode,
777
+ onChange
778
+ }) {
779
+ const tr = useTr();
780
+ return /* @__PURE__ */ jsxs(
781
+ "div",
782
+ {
783
+ className: "flex gap-2 text-xs",
784
+ role: "group",
785
+ "aria-label": tr(
786
+ "fluidgrids.executions.signal.payloadMode",
787
+ "Payload editor"
788
+ ),
789
+ children: [
790
+ /* @__PURE__ */ jsx(
791
+ Button,
792
+ {
793
+ variant: mode === "form" ? "default" : "outline",
794
+ size: "sm",
795
+ "aria-pressed": mode === "form",
796
+ onClick: () => onChange("form"),
797
+ children: tr("fluidgrids.executions.signal.formMode", "Form")
798
+ }
799
+ ),
800
+ /* @__PURE__ */ jsx(
801
+ Button,
802
+ {
803
+ variant: mode === "json" ? "default" : "outline",
804
+ size: "sm",
805
+ "aria-pressed": mode === "json",
806
+ onClick: () => onChange("json"),
807
+ children: tr("fluidgrids.executions.signal.jsonMode", "JSON")
808
+ }
809
+ )
810
+ ]
811
+ }
812
+ );
813
+ }
814
+ function SignalRunDialog({
815
+ runId,
816
+ targets: currentTargets,
817
+ open,
818
+ onOpenChange,
819
+ onSent,
820
+ initialName: currentInitialName
821
+ }) {
822
+ const tr = useTr();
823
+ const { targets, initialName } = useOpenSnapshot(open, {
824
+ targets: currentTargets,
825
+ initialName: currentInitialName
826
+ });
827
+ const firstName = initialName ?? targets.targets[0]?.name ?? CUSTOM;
828
+ const [selected, setSelected] = useState(firstName);
829
+ const [customName, setCustomName] = useState("");
830
+ const [mode, setMode] = useState("form");
831
+ const [rawPayload, setRawPayload] = useState("");
832
+ const [error, setError] = useState(null);
833
+ const [sending, setSending] = useState(false);
834
+ const [result, setResult] = useState(null);
835
+ const idempotencyKey = useRef(newSignalIdempotencyKey());
836
+ const cardRef = useRef(null);
837
+ useEffect(() => {
838
+ if (!open) return;
839
+ idempotencyKey.current = newSignalIdempotencyKey();
840
+ setSelected(firstName);
841
+ setCustomName("");
842
+ setRawPayload("");
843
+ setError(null);
844
+ setResult(null);
845
+ setSending(false);
846
+ }, [open, firstName]);
847
+ const target = targets.targets.find((t) => t.name === selected);
848
+ const name = selected === CUSTOM ? customName.trim() : selected;
849
+ const card = useMemo(
850
+ () => signalPayloadCard(target?.payloadSchema),
851
+ [target?.payloadSchema]
852
+ );
853
+ const effectiveMode = card ? mode : "json";
854
+ useEffect(() => {
855
+ idempotencyKey.current = newSignalIdempotencyKey();
856
+ setResult(null);
857
+ setError(null);
858
+ }, [selected]);
859
+ const send = async () => {
860
+ setError(null);
861
+ if (!name) {
862
+ setError(
863
+ tr("fluidgrids.executions.signal.nameRequired", "Enter a signal name.")
864
+ );
865
+ return;
866
+ }
867
+ let payload;
868
+ if (effectiveMode === "form" && card) {
869
+ if (cardRef.current && !cardRef.current.validateInputs()) {
870
+ setError(
871
+ tr(
872
+ "fluidgrids.executions.signal.formInvalid",
873
+ "Fill in the required fields."
874
+ )
875
+ );
876
+ return;
877
+ }
878
+ const parsed = payloadFromCardValues(
879
+ target?.payloadSchema,
880
+ cardRef.current?.collectValues() ?? {}
881
+ );
882
+ if ("error" in parsed) {
883
+ setError(parsed.error);
884
+ return;
885
+ }
886
+ payload = parsed.payload;
887
+ } else {
888
+ const parsed = parseRawSignalPayload(rawPayload);
889
+ if ("error" in parsed) {
890
+ setError(
891
+ tr(
892
+ "fluidgrids.executions.signal.jsonInvalid",
893
+ "The payload is not valid JSON: {{message}}",
894
+ { message: parsed.error }
895
+ )
896
+ );
897
+ return;
898
+ }
899
+ payload = parsed.payload;
900
+ }
901
+ setSending(true);
902
+ try {
903
+ const sent = await sendRunSignal({
904
+ runId,
905
+ name,
906
+ payload,
907
+ idempotencyKey: idempotencyKey.current
908
+ });
909
+ setResult(sent);
910
+ onSent?.(sent);
911
+ } catch (err) {
912
+ setError(describeOperationError(err));
913
+ } finally {
914
+ setSending(false);
915
+ }
916
+ };
917
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { "data-testid": "signal-run-dialog", className: "max-w-lg", children: [
918
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
919
+ /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.executions.signal.title", "Send a signal") }),
920
+ /* @__PURE__ */ jsx(DialogDescription, { children: tr(
921
+ "fluidgrids.executions.signal.description",
922
+ "The run receives the signal by name. A payload that does not match the signal's schema is refused."
923
+ ) })
924
+ ] }),
925
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
926
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
927
+ /* @__PURE__ */ jsx(Label, { htmlFor: "signal-name", children: tr("fluidgrids.executions.signal.nameLabel", "Signal") }),
928
+ /* @__PURE__ */ jsxs(
929
+ "select",
930
+ {
931
+ id: "signal-name",
932
+ value: selected,
933
+ onChange: (e) => setSelected(e.target.value),
934
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
935
+ "data-testid": "signal-name-select",
936
+ children: [
937
+ targets.targets.map((t) => /* @__PURE__ */ jsx("option", { value: t.name, children: t.name }, `${t.nodeId ?? ""}:${t.name}`)),
938
+ targets.openNames && /* @__PURE__ */ jsx("option", { value: CUSTOM, children: tr(
939
+ "fluidgrids.executions.signal.otherName",
940
+ "Another name\u2026"
941
+ ) })
942
+ ]
943
+ }
944
+ ),
945
+ selected === CUSTOM && /* @__PURE__ */ jsx(
946
+ CustomSignalNameInput,
947
+ {
948
+ value: customName,
949
+ onChange: setCustomName
950
+ }
951
+ )
952
+ ] }),
953
+ card && /* @__PURE__ */ jsx(PayloadModeToggle, { mode: effectiveMode, onChange: setMode }),
954
+ effectiveMode === "form" && card ? /* @__PURE__ */ jsx(components_default, { ref: cardRef, cardData: card }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
955
+ /* @__PURE__ */ jsx(Label, { htmlFor: "signal-payload", children: tr(
956
+ "fluidgrids.executions.signal.payloadLabel",
957
+ "Payload (JSON)"
958
+ ) }),
959
+ /* @__PURE__ */ jsx(
960
+ Textarea,
961
+ {
962
+ id: "signal-payload",
963
+ value: rawPayload,
964
+ onChange: (e) => setRawPayload(e.target.value),
965
+ rows: 6,
966
+ className: "font-mono text-sm",
967
+ placeholder: '{ "approved": true }',
968
+ "data-testid": "signal-payload-json"
969
+ }
970
+ )
971
+ ] }),
972
+ error && /* @__PURE__ */ jsx(
973
+ "p",
974
+ {
975
+ role: "alert",
976
+ className: "text-sm text-[var(--color-status-error-text)]",
977
+ children: error
978
+ }
979
+ ),
980
+ result && /* @__PURE__ */ jsxs(
981
+ "p",
982
+ {
983
+ "data-testid": "signal-result",
984
+ className: "flex items-center gap-2 text-sm text-[var(--color-status-success-text)]",
985
+ children: [
986
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:check-circle-outline", width: 16 }),
987
+ result.duplicate ? tr(
988
+ "fluidgrids.executions.signal.duplicate",
989
+ "Already sent; the run recorded it once."
990
+ ) : result.buffered ? tr(
991
+ "fluidgrids.executions.signal.buffered",
992
+ "Sent. The run will receive it when it reaches its wait."
993
+ ) : result.forwardedToRunId ? tr(
994
+ "fluidgrids.executions.signal.forwarded",
995
+ "Sent to the child run that is waiting for it."
996
+ ) : tr(
997
+ "fluidgrids.executions.signal.delivered",
998
+ "Sent and delivered."
999
+ )
1000
+ ]
1001
+ }
1002
+ )
1003
+ ] }),
1004
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1005
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: result ? tr("fluidgrids.executions.signal.close", "Close") : tr("fluidgrids.executions.signal.cancel", "Cancel") }),
1006
+ !result && /* @__PURE__ */ jsx(
1007
+ Button,
1008
+ {
1009
+ onClick: () => void send(),
1010
+ disabled: sending,
1011
+ "data-testid": "signal-send",
1012
+ children: sending ? tr("fluidgrids.executions.signal.sending", "Sending...") : tr("fluidgrids.executions.signal.send", "Send")
1013
+ }
1014
+ )
1015
+ ] })
1016
+ ] }) });
1017
+ }
1018
+ var RUN_HISTORY_POLL_INTERVAL_MS = 2e3;
1019
+ var RUN_HISTORY_SAFETY_READ_INITIAL_MS = 15e3;
1020
+ var RUN_HISTORY_SAFETY_READ_MAX_MS = 12e4;
1021
+ var RUN_HISTORY_PAGE_SIZE = 500;
1022
+ var RUN_HISTORY_MAX_PAGES = 20;
1023
+ function toEvents(events) {
1024
+ return events.map((event) => ({
1025
+ runId: event.runId,
1026
+ seq: event.seq,
1027
+ type: event.type,
1028
+ taskId: event.taskId ?? null,
1029
+ payload: event.payload ?? null,
1030
+ payloadUrl: event.payloadUrl ?? null,
1031
+ createdAt: event.createdAt
1032
+ }));
1033
+ }
1034
+ function useRunHistory(runId, options = {}) {
1035
+ const { live = false, enabled = true } = options;
1036
+ const [events, setEvents] = useState([]);
1037
+ const [loading, setLoading] = useState(enabled && !!runId);
1038
+ const [error, setError] = useState(null);
1039
+ const [truncated, setTruncated] = useState(false);
1040
+ const [historyLength, setHistoryLength] = useState(null);
1041
+ const [streaming, setStreaming] = useState(false);
1042
+ const cursor = useRef(0);
1043
+ const generation = useRef(0);
1044
+ const append = useCallback((incoming) => {
1045
+ if (!incoming.length) return;
1046
+ setEvents((current) => {
1047
+ const next = mergeHistoryEvents(current, incoming);
1048
+ cursor.current = Math.max(cursor.current, lastSeqOf(next));
1049
+ return next;
1050
+ });
1051
+ }, []);
1052
+ const readForward = useCallback(
1053
+ async (maxPages) => {
1054
+ const client = getFluidGridsClientSafe();
1055
+ if (!client || !runId) return;
1056
+ const started = generation.current;
1057
+ for (let page = 0; page < maxPages; page += 1) {
1058
+ const result = await client.query({
1059
+ query: WorkflowRunHistoryDocument,
1060
+ variables: {
1061
+ runId,
1062
+ after: cursor.current,
1063
+ limit: RUN_HISTORY_PAGE_SIZE
1064
+ },
1065
+ fetchPolicy: "network-only"
1066
+ });
1067
+ throwIfResultError(result);
1068
+ if (generation.current !== started) return;
1069
+ const pageData = result.data?.workflowRunHistory;
1070
+ if (!pageData) return;
1071
+ const fresh = toEvents(pageData.events);
1072
+ append(fresh);
1073
+ cursor.current = Math.max(
1074
+ cursor.current,
1075
+ lastSeqOf(fresh),
1076
+ pageData.nextAfter ?? 0
1077
+ );
1078
+ setHistoryLength(pageData.historyLength);
1079
+ if (!pageData.hasMore || fresh.length === 0) {
1080
+ setTruncated(false);
1081
+ return;
1082
+ }
1083
+ if (page === maxPages - 1) setTruncated(true);
1084
+ }
1085
+ },
1086
+ [runId, append]
1087
+ );
1088
+ const refetch = useCallback(async () => {
1089
+ generation.current += 1;
1090
+ cursor.current = 0;
1091
+ setEvents([]);
1092
+ setTruncated(false);
1093
+ setError(null);
1094
+ setLoading(true);
1095
+ try {
1096
+ await readForward(RUN_HISTORY_MAX_PAGES);
1097
+ } catch (err) {
1098
+ setError(err instanceof Error ? err : new Error(String(err)));
1099
+ } finally {
1100
+ setLoading(false);
1101
+ }
1102
+ }, [readForward]);
1103
+ useEffect(() => {
1104
+ if (!runId || !enabled) {
1105
+ generation.current += 1;
1106
+ cursor.current = 0;
1107
+ setEvents([]);
1108
+ setLoading(false);
1109
+ return;
1110
+ }
1111
+ void refetch();
1112
+ }, [runId, enabled, refetch]);
1113
+ useEffect(() => {
1114
+ const client = getFluidGridsClientSafe();
1115
+ if (!runId || !enabled || !live || !client) {
1116
+ setStreaming(false);
1117
+ return;
1118
+ }
1119
+ let cancelled = false;
1120
+ let poll = null;
1121
+ let safetyRead = null;
1122
+ let delivered = false;
1123
+ const stopSafetyReads = () => {
1124
+ if (safetyRead) clearTimeout(safetyRead);
1125
+ safetyRead = null;
1126
+ };
1127
+ const startPolling = () => {
1128
+ stopSafetyReads();
1129
+ if (poll || cancelled) return;
1130
+ setStreaming(false);
1131
+ poll = setInterval(() => {
1132
+ void readForward(1).catch(() => {
1133
+ });
1134
+ }, RUN_HISTORY_POLL_INTERVAL_MS);
1135
+ };
1136
+ const scheduleSafetyRead = (delay) => {
1137
+ safetyRead = setTimeout(() => {
1138
+ safetyRead = null;
1139
+ if (cancelled || delivered || poll) return;
1140
+ void readForward(1).catch(() => {
1141
+ });
1142
+ scheduleSafetyRead(Math.min(delay * 2, RUN_HISTORY_SAFETY_READ_MAX_MS));
1143
+ }, delay);
1144
+ };
1145
+ const subscription = client.subscribe({ query: RunEventAppendedDocument, variables: { runId } }).subscribe({
1146
+ next: ({ data }) => {
1147
+ if (cancelled || !data?.runEventAppended) return;
1148
+ delivered = true;
1149
+ stopSafetyReads();
1150
+ if (poll) {
1151
+ clearInterval(poll);
1152
+ poll = null;
1153
+ }
1154
+ setStreaming(true);
1155
+ append(toEvents([data.runEventAppended]));
1156
+ },
1157
+ error: () => startPolling(),
1158
+ complete: () => {
1159
+ if (!cancelled) startPolling();
1160
+ }
1161
+ });
1162
+ scheduleSafetyRead(RUN_HISTORY_SAFETY_READ_INITIAL_MS);
1163
+ return () => {
1164
+ cancelled = true;
1165
+ stopSafetyReads();
1166
+ if (poll) clearInterval(poll);
1167
+ subscription.unsubscribe();
1168
+ setStreaming(false);
1169
+ };
1170
+ }, [runId, enabled, live, readForward, append]);
1171
+ const wasLive = useRef(false);
1172
+ useEffect(() => {
1173
+ if (live) {
1174
+ wasLive.current = true;
1175
+ return;
1176
+ }
1177
+ if (!wasLive.current) return;
1178
+ wasLive.current = false;
1179
+ void readForward(RUN_HISTORY_MAX_PAGES).catch(() => {
1180
+ });
1181
+ }, [live, readForward]);
1182
+ return {
1183
+ events,
1184
+ loading,
1185
+ error,
1186
+ truncated,
1187
+ historyLength,
1188
+ streaming,
1189
+ refetch
1190
+ };
1191
+ }
1192
+
1193
+ export { RunHistoryView, SignalRunDialog, terminateDurableRun, useDeterminismStatus, useDurableRunFields, useRunHistory, useRunStackTrace };