@burdenoff/fluidgrids-fe-sdk 2026.1111.4 → 2026.1111.5

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 (44) hide show
  1. package/dist/{ExecutionDetailPage-CdcutZhM.js → ExecutionDetailPage-CKmm-MTf.js} +602 -33
  2. package/dist/{FailureTriagePanel-4z3sJbF-.js → FailureTriagePanel-LodnXzJr.js} +2 -2
  3. package/dist/{FluidGridsProvider-D4VBLmsU.js → FluidGridsProvider-BNzMznqt.js} +1 -1
  4. package/dist/{HistoryPage-DsuGx50x.js → HistoryPage-DIioKCod.js} +3 -3
  5. package/dist/{InsightsPage-DPSE3P7m.js → InsightsPage-Bm2oCTqf.js} +6 -6
  6. package/dist/{ManagedAiPolicyAuthority-Dc9UeQPh.js → ManagedAiPolicyAuthority-D624rJtj.js} +7 -7
  7. package/dist/{MissingNodesInstallModal-DLaS7YqW.js → MissingNodesInstallModal-5iwp-P3H.js} +2 -2
  8. package/dist/{NodesPage-C2woL-6F.js → NodesPage-CYijlEFV.js} +2 -2
  9. package/dist/{PublishCustomNodeAction-ByxKmyOF.js → PublishCustomNodeAction-CGrwIjy-.js} +1 -1
  10. package/dist/{TemplateConfigurationModal-Cc9ULJd8.js → TemplateConfigurationModal-BRjVScYa.js} +2 -2
  11. package/dist/{TemplateDetailPanel-CnlspQbn.js → TemplateDetailPanel-BP43BIne.js} +3 -3
  12. package/dist/{TemplatesPage-CZdxYIcf.js → TemplatesPage-Bw--Ybm9.js} +6 -6
  13. package/dist/{VersionDiffDialog-B6JNtNM1.js → VersionDiffDialog-87FMkJQb.js} +1 -1
  14. package/dist/{WorkflowProposalCard-Ci5kwjR4.js → WorkflowProposalCard-1Av72TtA.js} +2 -2
  15. package/dist/{WorkflowSearchRedirect-CoCTVuHn.js → WorkflowSearchRedirect-IDjgBWKT.js} +10 -10
  16. package/dist/{WorkflowShareDialog-DknKAi9x.js → WorkflowShareDialog-EsWpP8h1.js} +3 -3
  17. package/dist/{WorkflowsList-BzCLsBR-.js → WorkflowsList-C-beGZ58.js} +6 -6
  18. package/dist/{agent-form-hb7BR-JZ.js → agent-form-IKft_4iz.js} +1 -1
  19. package/dist/{agentTools-NEBmfRWF.js → agentTools-BQY87HJw.js} +2 -2
  20. package/dist/{ai-usage-CK61EZWc.js → ai-usage-CMWDlKmM.js} +2 -2
  21. package/dist/{codec-client-C3LkdZvc.js → codec-client-DEBa-Wl0.js} +1 -1
  22. package/dist/executions.js +1 -1
  23. package/dist/{flow-builder-28Hp8NgO.js → flow-builder-DOw2DgVO.js} +23 -23
  24. package/dist/{graphql-types-CulUbQWN.js → graphql-types-Bi7XFTD2.js} +18 -1
  25. package/dist/history.js +1 -1
  26. package/dist/{index-lX6U7gi1.d.ts → index-CavhNsSY.d.ts} +155 -1
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +25 -25
  29. package/dist/{node-detail-view-CVkO4pda.js → node-detail-view-D56v1bff.js} +8 -8
  30. package/dist/{node-endpoint-configs-DS59aLlH.js → node-endpoint-configs-Dol5ckHv.js} +812 -1
  31. package/dist/nodes.js +1 -1
  32. package/dist/observability.d.ts +1 -1
  33. package/dist/observability.js +1 -1
  34. package/dist/{quick-config-panel-CpjVDTyF.js → quick-config-panel-BhkLeEy4.js} +2 -2
  35. package/dist/settings.js +1 -1
  36. package/dist/templates.js +1 -1
  37. package/dist/{useAgentDefinitions-SRq-nq4y.js → useAgentDefinitions-CUXSBw0L.js} +1 -1
  38. package/dist/{useExecutions-BdDmxTNK.js → useExecutions-CvGl7yhb.js} +3 -3
  39. package/dist/{useObservability-CrZ-YuBS.js → useObservability--lPyvKqs.js} +2 -2
  40. package/dist/{useRunHistory-BVxQmQ6g.js → useRunHistory-Cu_oiZBw.js} +3 -3
  41. package/dist/{useWorkflows-BfDQmAeS.js → useWorkflows-CGF0B59E.js} +2 -2
  42. package/dist/workflow-builder.js +2 -2
  43. package/dist/workflows-list.js +1 -1
  44. package/package.json +1 -1
@@ -1,21 +1,21 @@
1
1
  "use client";
2
2
  import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
- import { $ as DurableParityRunFieldsDocument, $i as useExecutionsStore, B as DeleteTaskQueueDocument, Ci as getFluidGridsClientSafe, D as CreateWorkerTokenDocument, E as CreateTaskQueueDocument, En as SetWorkflowRunVersioningOverrideDocument, Kr as WorkflowRunMetadataDocument, Li as throwIfResultError, Pi as describeOperationError, Qi as DEFAULT_EXECUTIONS_FILTER, Rn as TaskQueueStatsDocument, Si as getFluidGridsClient, Ur as WorkflowRunHierarchyDocument, V as DeleteWorkerTokenDocument, Vi as getFluidGridsConfig, Xr as WorkflowRunsCountDocument, _i as WorkflowWorkersDocument, _n as RunLogsDocument, ct as ExplainRunDocument, er as UpdateTaskQueueDocument, gr as WorkflowBatchOperationUpdatedDocument, hn as RunLogAppendedDocument, lr as WorkerTokensDocument, on as RotateWorkerTokenDocument, qr as WorkflowRunPendingDocument, sn as RunAiUsageDocument, wn as SetWorkerTokenConnectionAllowlistDocument, zn as TaskQueuesDocument } from "./node-endpoint-configs-DS59aLlH.js";
4
- import { $t as runDurationMs, Hn as useDurableFunctionNodesEnabled, Pn as runSignalTargets, Qt as nodeStateDurationMs, Un as useDurableParityEnabled, Yn as useWorkflowTestsEnabled, Zt as formatElapsedMs, jn as durableInterfaceOf, kn as canSignalRun, qn as usePhaseCFlags } from "./FluidGridsProvider-D4VBLmsU.js";
3
+ import { $r as WorkflowRunsCountDocument, A as CreateWorkerTokenDocument, Bi as throwIfResultError, Dn as SetWorkerTokenConnectionAllowlistDocument, Ei as getFluidGridsClientSafe, Hn as TaskQueuesDocument, Kr as WorkflowRunHierarchyDocument, Li as describeOperationError, Ti as getFluidGridsClient, U as DeleteTaskQueueDocument, Vn as TaskQueueStatsDocument, W as DeleteWorkerTokenDocument, Wi as getFluidGridsConfig, Xr as WorkflowRunPendingDocument, Yr as WorkflowRunMetadataDocument, bi as WorkflowWorkersDocument, bn as RunLogsDocument, c as AgentTraceDocument, dt as ExplainRunDocument, fr as WorkerTokensDocument, k as CreateTaskQueueDocument, kn as SetWorkflowRunVersioningOverrideDocument, ln as RotateWorkerTokenDocument, na as useExecutionsStore, nt as DurableParityRunFieldsDocument, rr as UpdateTaskQueueDocument, s as AgentStepUpdatedDocument, ta as DEFAULT_EXECUTIONS_FILTER, un as RunAiUsageDocument, vn as RunLogAppendedDocument, yr as WorkflowBatchOperationUpdatedDocument } from "./node-endpoint-configs-Dol5ckHv.js";
4
+ import { $t as runDurationMs, Hn as useDurableFunctionNodesEnabled, Pn as runSignalTargets, Qt as nodeStateDurationMs, Un as useDurableParityEnabled, Yn as useWorkflowTestsEnabled, Zt as formatElapsedMs, jn as durableInterfaceOf, kn as canSignalRun, qn as usePhaseCFlags } from "./FluidGridsProvider-BNzMznqt.js";
5
5
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
6
- import { h as useWorkflowDefinitions, v as useWorkflowVersions } from "./useWorkflows-BfDQmAeS.js";
7
- import { KF as WorkflowUpdateWaitFor, OF as WorkflowQueryRejectCondition, Qu as ErrorType, dF as WorkflowBatchOperationType, fI as useFluidGridsPermission, kF as WorkflowResetType, lI as SdkPermissionButton, oI as FLUIDGRIDS_PERMISSIONS, pP as VersioningBehavior } from "./graphql-types-CulUbQWN.js";
8
- import { _ as statusCountFilter, c as OPERATORS_BY_TYPE, d as STATUS_COUNT_GROUP_BY, f as callbackTone, g as shortRunId, h as runChainLinks, i as useExecutionStats, l as buildSearchAttributeFilter, m as parseMetadataDefinition, n as useExecution, o as useExecutionsWithStore, p as overrideRequestOf, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, v as statusCountsOf, y as validateDateRange } from "./useExecutions-BdDmxTNK.js";
6
+ import { h as useWorkflowDefinitions, v as useWorkflowVersions } from "./useWorkflows-CGF0B59E.js";
7
+ import { $u as ErrorType, AF as WorkflowResetType, L as AgentStepKind, fF as WorkflowBatchOperationType, kF as WorkflowQueryRejectCondition, mP as VersioningBehavior, pI as useFluidGridsPermission, qF as WorkflowUpdateWaitFor, sI as FLUIDGRIDS_PERMISSIONS, uI as SdkPermissionButton } from "./graphql-types-Bi7XFTD2.js";
8
+ import { _ as statusCountFilter, c as OPERATORS_BY_TYPE, d as STATUS_COUNT_GROUP_BY, f as callbackTone, g as shortRunId, h as runChainLinks, i as useExecutionStats, l as buildSearchAttributeFilter, m as parseMetadataDefinition, n as useExecution, o as useExecutionsWithStore, p as overrideRequestOf, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, v as statusCountsOf, y as validateDateRange } from "./useExecutions-CvGl7yhb.js";
9
9
  import { n as useFluidGridsPageControls } from "./pageControlsBridge-0pX9s-3k.js";
10
10
  import { t as executionsControls } from "./pageControlSpecs-DZJ9K37m.js";
11
11
  import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
12
- import { $ as startBatchTerminate, A as fetchBatchOperation, B as jsonArg, F as fetchRunControlFields, G as resetRun, I as fetchRunHistoryArchive, K as resetRunToLastCleanTask, L as fetchRunUpdate, N as fetchCodecServerSettings, O as failActivityById, P as fetchPauseRules, Q as startBatchOperation, R as fetchSearchAttributes, U as queryRun, V as pauseActivities, W as resetActivities, X as sendRunUpdate, Z as startBatchExport, _ as useResource, at as readTyped, c as FieldError, d as NativeSelect, f as RUN_CONTROL_DIALOG_STYLE, g as parseJsonObjectInput, h as parseJsonInput, it as upsertRunSearchAttributes, j as fetchBatchOperations, l as JsonBlock, m as newRunControlId, ot as writeTyped, p as StatusPill, q as revokeSignalToken, rt as updateActivityOptions, s as isAgentNodeType, tt as unpauseActivities, u as JsonField, w as createSignalToken, x as completeActivityById, y as cancelBatchOperation, z as fetchSignalTokens } from "./agent-form-hb7BR-JZ.js";
13
- import { C as useAvailableTags, _ as buildNodeDisplayMap, b as EntityTagPicker, n as useWorkflowProposal, o as canSaveRunAsTestCase, r as saveRunAsTestCase, t as WorkflowProposalCard, v as formatRunVersion, y as nodeDisplayTitle } from "./WorkflowProposalCard-Ci5kwjR4.js";
12
+ import { $ as startBatchTerminate, A as fetchBatchOperation, B as jsonArg, F as fetchRunControlFields, G as resetRun, I as fetchRunHistoryArchive, K as resetRunToLastCleanTask, L as fetchRunUpdate, N as fetchCodecServerSettings, O as failActivityById, P as fetchPauseRules, Q as startBatchOperation, R as fetchSearchAttributes, U as queryRun, V as pauseActivities, W as resetActivities, X as sendRunUpdate, Z as startBatchExport, _ as useResource, at as readTyped, c as FieldError, d as NativeSelect, f as RUN_CONTROL_DIALOG_STYLE, g as parseJsonObjectInput, h as parseJsonInput, it as upsertRunSearchAttributes, j as fetchBatchOperations, l as JsonBlock, m as newRunControlId, ot as writeTyped, p as StatusPill, q as revokeSignalToken, rt as updateActivityOptions, s as isAgentNodeType, tt as unpauseActivities, u as JsonField, w as createSignalToken, x as completeActivityById, y as cancelBatchOperation, z as fetchSignalTokens } from "./agent-form-IKft_4iz.js";
13
+ import { C as useAvailableTags, _ as buildNodeDisplayMap, b as EntityTagPicker, n as useWorkflowProposal, o as canSaveRunAsTestCase, r as saveRunAsTestCase, t as WorkflowProposalCard, v as formatRunVersion, y as nodeDisplayTitle } from "./WorkflowProposalCard-1Av72TtA.js";
14
14
  import { t as AiCreditBalanceSlot } from "./aiCreditsBridge-BuE92tKo.js";
15
- import { a as fetchRunFailureClusters, i as dismissRunFailureCluster, o as runListFilterFromSearch, r as OPEN_STATUSES, s as runListLinkKey, t as BUCKET_LABELS } from "./FailureTriagePanel-4z3sJbF-.js";
16
- import { a as encodedPayloadBytes, f as EffectBadge, h as useRunPolicyDecisions, i as decodePayloads, l as formatBudgetUsd, n as checkCodecServerUrl, o as isEncodedPayload, r as containsEncodedPayload, t as CodecServerError } from "./codec-client-C3LkdZvc.js";
17
- import { a as useDurableRunFields, i as useDeterminismStatus, n as SignalRunDialog, o as useRunStackTrace, r as terminateDurableRun, s as RunHistoryView, t as useRunHistory } from "./useRunHistory-BVxQmQ6g.js";
18
- import { a as formatTokens, o as modelLabel, r as aiCost } from "./ai-usage-CK61EZWc.js";
15
+ import { a as fetchRunFailureClusters, i as dismissRunFailureCluster, o as runListFilterFromSearch, r as OPEN_STATUSES, s as runListLinkKey, t as BUCKET_LABELS } from "./FailureTriagePanel-LodnXzJr.js";
16
+ import { a as encodedPayloadBytes, f as EffectBadge, h as useRunPolicyDecisions, i as decodePayloads, l as formatBudgetUsd, n as checkCodecServerUrl, o as isEncodedPayload, r as containsEncodedPayload, t as CodecServerError } from "./codec-client-DEBa-Wl0.js";
17
+ import { a as useDurableRunFields, i as useDeterminismStatus, n as SignalRunDialog, o as useRunStackTrace, r as terminateDurableRun, s as RunHistoryView, t as useRunHistory } from "./useRunHistory-Cu_oiZBw.js";
18
+ import { a as formatTokens, d as formatMicroUsd, o as modelLabel, r as aiCost } from "./ai-usage-CMWDlKmM.js";
19
19
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
20
  import { Link, useNavigate, useParams, useSearchParams } from "react-router";
21
21
  import { Icon } from "@iconify/react";
@@ -1633,14 +1633,14 @@ function ExecutionTimeline({ executions, loading, onItemClick, onAction }) {
1633
1633
  * instead of `steps`. Output is free-form JSON, so every read is defensive:
1634
1634
  * a field of an unexpected shape is skipped, never thrown on.
1635
1635
  */
1636
- function isRecord$2(value) {
1636
+ function isRecord$3(value) {
1637
1637
  return typeof value === "object" && value !== null && !Array.isArray(value);
1638
1638
  }
1639
1639
  function asRecord(value) {
1640
- if (isRecord$2(value)) return value;
1640
+ if (isRecord$3(value)) return value;
1641
1641
  if (typeof value === "string") {
1642
1642
  const parsed = tryParseJson(value);
1643
- return isRecord$2(parsed) ? parsed : null;
1643
+ return isRecord$3(parsed) ? parsed : null;
1644
1644
  }
1645
1645
  return null;
1646
1646
  }
@@ -1702,7 +1702,7 @@ function classifyObservation(observation) {
1702
1702
  };
1703
1703
  }
1704
1704
  function toStep(raw, index) {
1705
- if (!isRecord$2(raw)) return null;
1705
+ if (!isRecord$3(raw)) return null;
1706
1706
  const tool = stringOrNull(raw.tool) ?? stringOrNull(raw.toolName) ?? stringOrNull(raw.action) ?? stringOrNull(raw.name);
1707
1707
  if (!tool) return null;
1708
1708
  const observationRaw = raw.observation ?? raw.output ?? raw.result;
@@ -1731,7 +1731,7 @@ function toPendingApprovals(raw) {
1731
1731
  if (!Array.isArray(raw)) return [];
1732
1732
  const pending = [];
1733
1733
  for (const item of raw) {
1734
- if (!isRecord$2(item)) continue;
1734
+ if (!isRecord$3(item)) continue;
1735
1735
  const tool = stringOrNull(item.name) ?? stringOrNull(item.tool);
1736
1736
  if (!tool) continue;
1737
1737
  const callId = stringOrNull(item.id) ?? stringOrNull(item.callId);
@@ -1753,7 +1753,7 @@ const PAYLOAD_KEYS = [
1753
1753
  "intermediateSteps"
1754
1754
  ];
1755
1755
  function looksLikeAgentPayload(value) {
1756
- return isRecord$2(value) && PAYLOAD_KEYS.some((key) => key in value);
1756
+ return isRecord$3(value) && PAYLOAD_KEYS.some((key) => key in value);
1757
1757
  }
1758
1758
  /**
1759
1759
  * The agent payload inside a node output: the output itself when it carries
@@ -5073,7 +5073,7 @@ function RunDataPrunedNotice({ dataPrunedAt }) {
5073
5073
  }
5074
5074
 
5075
5075
  // src/modules/executions/utils/run-queue-marker.ts
5076
- function isRecord$1(value) {
5076
+ function isRecord$2(value) {
5077
5077
  return typeof value === "object" && value !== null && !Array.isArray(value);
5078
5078
  }
5079
5079
  function isRunQueueScope(value) {
@@ -5081,9 +5081,9 @@ function isRunQueueScope(value) {
5081
5081
  }
5082
5082
  /** The queue marker on a run's context, or null when there is none or it is malformed. */
5083
5083
  function readRunQueueMarker(context) {
5084
- if (!isRecord$1(context)) return null;
5084
+ if (!isRecord$2(context)) return null;
5085
5085
  const marker = context.concurrency;
5086
- if (!isRecord$1(marker)) return null;
5086
+ if (!isRecord$2(marker)) return null;
5087
5087
  const { queuedAt, checkedAt, limit, active, position, scope } = marker;
5088
5088
  if (typeof queuedAt !== "string" || typeof checkedAt !== "string" || typeof limit !== "number" || typeof active !== "number" || typeof position !== "number" || scope !== void 0 && !isRunQueueScope(scope)) return null;
5089
5089
  return {
@@ -5903,19 +5903,199 @@ function selectionForRun(selection, runId, requested, fallback) {
5903
5903
  };
5904
5904
  }
5905
5905
 
5906
+ // src/modules/executions/utils/agent-steps.ts
5907
+ function isRecord$1(value) {
5908
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5909
+ }
5910
+ function text$1(value) {
5911
+ return typeof value === "string" && value.length > 0 ? value : null;
5912
+ }
5913
+ /** Every step of a trace, as one flat list. */
5914
+ function flattenAgentTrace(trace) {
5915
+ if (!trace) return [];
5916
+ return trace.nodes.flatMap((node) => node.iterations.flatMap((iteration) => iteration.steps));
5917
+ }
5918
+ /**
5919
+ * `current` with `incoming` added. A step already held keeps the newer copy
5920
+ * only when that copy carries a cost the held one lacks (a re-read after the
5921
+ * ledger priced the call); otherwise the held copy stays.
5922
+ */
5923
+ function mergeAgentSteps(current, incoming) {
5924
+ const next = new Map(current);
5925
+ for (const step of incoming) {
5926
+ const held = next.get(step.id);
5927
+ if (!held || held.costMicroUsd === null && step.costMicroUsd !== null) next.set(step.id, step);
5928
+ }
5929
+ return next;
5930
+ }
5931
+ function sumCost(values) {
5932
+ const priced = values.filter((value) => value !== null);
5933
+ return priced.length ? priced.reduce((sum, value) => sum + value, 0) : null;
5934
+ }
5935
+ function toTurn(step) {
5936
+ const output = step.output;
5937
+ return {
5938
+ key: step.id,
5939
+ model: step.model,
5940
+ tokensIn: step.tokensIn,
5941
+ tokensOut: step.tokensOut,
5942
+ latencyMs: step.latencyMs,
5943
+ costMicroUsd: step.costMicroUsd,
5944
+ input: prettyValue(step.input),
5945
+ reply: isRecord$1(output) ? text$1(output.content) ?? prettyValue(output.toolCalls) ?? text$1(output.error) : prettyValue(output)
5946
+ };
5947
+ }
5948
+ function toFinal(step) {
5949
+ const output = step.output;
5950
+ if (!isRecord$1(output)) return {
5951
+ answer: prettyValue(output),
5952
+ structuredOutput: null,
5953
+ outputErrors: []
5954
+ };
5955
+ return {
5956
+ answer: text$1(output.answer),
5957
+ structuredOutput: prettyValue(output.structuredOutput),
5958
+ outputErrors: Array.isArray(output.outputErrors) ? output.outputErrors.filter((error) => typeof error === "string") : []
5959
+ };
5960
+ }
5961
+ function errorOf(step) {
5962
+ const output = step.output;
5963
+ return isRecord$1(output) ? text$1(output.error) : text$1(output);
5964
+ }
5965
+ function buildIteration(iteration, steps) {
5966
+ const turns = [];
5967
+ const toolCalls = [];
5968
+ const byCallId = /* @__PURE__ */ new Map();
5969
+ let final = null;
5970
+ let error = null;
5971
+ for (const step of steps) switch (step.kind) {
5972
+ case "PLAN":
5973
+ turns.push(toTurn(step));
5974
+ break;
5975
+ case "TOOL_CALL": {
5976
+ const output = step.output;
5977
+ const view = {
5978
+ key: step.id,
5979
+ callId: step.toolCallId,
5980
+ tool: step.toolName ?? "?",
5981
+ args: prettyValue(step.input),
5982
+ rule: isRecord$1(output) ? text$1(output.rule) : null,
5983
+ result: null,
5984
+ hasResult: false,
5985
+ outcome: "ok",
5986
+ reason: null,
5987
+ latencyMs: null,
5988
+ approvalId: null
5989
+ };
5990
+ toolCalls.push(view);
5991
+ if (step.toolCallId) byCallId.set(step.toolCallId, view);
5992
+ break;
5993
+ }
5994
+ case "TOOL_RESULT": {
5995
+ const { outcome, reason } = classifyObservation(step.output);
5996
+ const result = {
5997
+ result: prettyValue(step.output),
5998
+ hasResult: true,
5999
+ outcome,
6000
+ reason,
6001
+ latencyMs: step.latencyMs,
6002
+ approvalId: step.approvalId
6003
+ };
6004
+ const call = step.toolCallId ? byCallId.get(step.toolCallId) : void 0;
6005
+ if (call) Object.assign(call, result);
6006
+ else toolCalls.push({
6007
+ key: step.id,
6008
+ callId: step.toolCallId,
6009
+ tool: step.toolName ?? "?",
6010
+ args: null,
6011
+ rule: null,
6012
+ ...result
6013
+ });
6014
+ break;
6015
+ }
6016
+ case "FINAL":
6017
+ final = toFinal(step);
6018
+ break;
6019
+ case "ERROR": error = errorOf(step);
6020
+ }
6021
+ return {
6022
+ iteration,
6023
+ turns,
6024
+ toolCalls,
6025
+ final,
6026
+ error,
6027
+ tokensIn: turns.reduce((sum, turn) => sum + (turn.tokensIn ?? 0), 0),
6028
+ tokensOut: turns.reduce((sum, turn) => sum + (turn.tokensOut ?? 0), 0),
6029
+ latencyMs: steps.reduce((sum, step) => sum + (step.latencyMs ?? 0), 0),
6030
+ costMicroUsd: sumCost(turns.map((turn) => turn.costMicroUsd))
6031
+ };
6032
+ }
6033
+ function nodeKey(step) {
6034
+ return step.nodeStateId ?? `node:${step.nodeId}`;
6035
+ }
6036
+ /**
6037
+ * The steps grouped by node execution (in the order the executions started)
6038
+ * and iteration. A tool result recorded after an approval pause belongs to
6039
+ * the iteration of its call.
6040
+ */
6041
+ function groupAgentSteps(steps) {
6042
+ const byNode = /* @__PURE__ */ new Map();
6043
+ for (const step of steps) {
6044
+ const key = nodeKey(step);
6045
+ const list = byNode.get(key);
6046
+ if (list) list.push(step);
6047
+ else byNode.set(key, [step]);
6048
+ }
6049
+ const nodes = [];
6050
+ for (const list of byNode.values()) {
6051
+ list.sort((a, b) => a.sequence - b.sequence);
6052
+ const byIteration = /* @__PURE__ */ new Map();
6053
+ for (const step of list) {
6054
+ const group = byIteration.get(step.iteration);
6055
+ if (group) group.push(step);
6056
+ else byIteration.set(step.iteration, [step]);
6057
+ }
6058
+ const iterations = [...byIteration.entries()].sort(([a], [b]) => a - b).map(([iteration, group]) => buildIteration(iteration, group));
6059
+ const first = list[0];
6060
+ if (!first) continue;
6061
+ nodes.push({
6062
+ nodeId: first.nodeId,
6063
+ nodeStateId: first.nodeStateId,
6064
+ iterations,
6065
+ totals: {
6066
+ iterations: iterations.length,
6067
+ modelCalls: iterations.reduce((sum, it) => sum + it.turns.length, 0),
6068
+ toolCalls: iterations.reduce((sum, it) => sum + it.toolCalls.length, 0),
6069
+ tokensIn: iterations.reduce((sum, it) => sum + it.tokensIn, 0),
6070
+ tokensOut: iterations.reduce((sum, it) => sum + it.tokensOut, 0),
6071
+ costMicroUsd: sumCost(iterations.map((it) => it.costMicroUsd))
6072
+ },
6073
+ startedAt: list.reduce((earliest, step) => step.createdAt < earliest ? step.createdAt : earliest, first.createdAt)
6074
+ });
6075
+ }
6076
+ return nodes.sort((a, b) => a.startedAt.localeCompare(b.startedAt)).map(({ startedAt: _startedAt, ...node }) => node);
6077
+ }
6078
+ /** The step trace of one node execution, when the run recorded one. */
6079
+ function stepTraceFor(nodes, nodeStateId, nodeId) {
6080
+ return nodes.find((node) => node.nodeStateId === nodeStateId) ?? nodes.find((node) => node.nodeStateId === null && node.nodeId === nodeId) ?? null;
6081
+ }
6082
+
5906
6083
  // src/modules/executions/components/AgentTracePanel.tsx
5907
6084
  /**
5908
6085
  * The run page's Agent tab (BOFF-9937): the run's AI usage roll-up and, per
5909
6086
  * AI Agent node, what the agent did — its tool calls in order, the final
5910
6087
  * response, and the calls waiting for a person's approval.
5911
6088
  *
5912
- * Read-only. The roll-up comes from `runAiUsage`; the per-node trace from the
5913
- * node states the run page already polls (see utils/agent-trace.ts). Per-call
5914
- * prompts and completions and per-iteration timing need the trace API
5915
- * (BOFF-9936) and are not shown or guessed here.
6089
+ * Read-only. The roll-up comes from `runAiUsage`. The per-node trace comes
6090
+ * from the step trace (`agentTrace`, BOFF-9936; see utils/agent-steps.ts):
6091
+ * each iteration's model calls with their tokens, timing and cost, what was
6092
+ * sent to the model and its reply, the tool calls and the final answer. A run
6093
+ * without a step trace (recorded before it existed) falls back to the node
6094
+ * states the run page already polls (see utils/agent-trace.ts).
5916
6095
  */
5917
- function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError, onRefreshUsage, focusNodeId }) {
6096
+ function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError, onRefreshUsage, focusNodeId, stepTraces = [], stepTraceDataPruned = false, stepTraceError = null }) {
5918
6097
  const tr = useTr();
6098
+ const anyStepTrace = traces.some((trace) => !!stepTraceFor(stepTraces, trace.nodeStateId, trace.nodeId));
5919
6099
  return /* @__PURE__ */ jsxs("div", {
5920
6100
  className: "space-y-6",
5921
6101
  "data-testid": "agent-trace-panel",
@@ -5928,15 +6108,25 @@ function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError,
5928
6108
  nodeDisplay,
5929
6109
  tr
5930
6110
  }),
6111
+ stepTraceError && /* @__PURE__ */ jsx("div", {
6112
+ role: "alert",
6113
+ className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
6114
+ children: tr("fluidgrids.executions.executionDetail.agent.stepTraceError", "Could not load the agent's steps: {{message}}", { message: stepTraceError })
6115
+ }),
6116
+ anyStepTrace && stepTraceDataPruned && /* @__PURE__ */ jsx("p", {
6117
+ className: "text-sm text-text-secondary",
6118
+ children: tr("fluidgrids.executions.executionDetail.agent.stepDataPruned", "This run's data settings removed what each step sent and received. Models, tokens, timing and tools are still shown.")
6119
+ }),
5931
6120
  traces.map((trace) => /* @__PURE__ */ jsx(AgentNodeCard, {
5932
6121
  trace,
6122
+ steps: stepTraceFor(stepTraces, trace.nodeStateId, trace.nodeId),
5933
6123
  title: nodeDisplayTitle(nodeDisplay[trace.nodeId], trace.nodeId),
5934
6124
  focused: !!focusNodeId && focusNodeId === trace.nodeId,
5935
6125
  tr
5936
6126
  }, trace.nodeStateId)),
5937
- /* @__PURE__ */ jsx("p", {
6127
+ !anyStepTrace && /* @__PURE__ */ jsx("p", {
5938
6128
  className: "text-xs text-text-secondary",
5939
- children: tr("fluidgrids.executions.executionDetail.agent.traceApiNote", "Each tool call is shown as the agent recorded it. The prompt and completion of each model call, and the timing of each iteration, will appear here once agent tracing is available.")
6129
+ children: tr("fluidgrids.executions.executionDetail.agent.noStepTrace", "This run has no step trace, so each tool call is shown as the node recorded it, without the model calls.")
5940
6130
  })
5941
6131
  ]
5942
6132
  });
@@ -6109,7 +6299,10 @@ function UsageLines({ lines, nodeDisplay, tr }) {
6109
6299
  })
6110
6300
  });
6111
6301
  }
6112
- function AgentNodeCard({ trace, title, focused, tr }) {
6302
+ function AgentNodeCard({ trace, steps, title, focused, tr }) {
6303
+ const iterationCount = steps?.totals.iterations ?? trace.iterationCount;
6304
+ const tokensUsed = steps ? steps.totals.tokensIn + steps.totals.tokensOut : trace.tokensUsed;
6305
+ const toolCallCount = steps?.totals.toolCalls ?? trace.steps.length;
6113
6306
  const ref = useRef(null);
6114
6307
  useEffect(() => {
6115
6308
  if (focused) ref.current?.scrollIntoView({ block: "start" });
@@ -6153,15 +6346,15 @@ function AgentNodeCard({ trace, title, focused, tr }) {
6153
6346
  children: [
6154
6347
  /* @__PURE__ */ jsx(Fact, {
6155
6348
  label: tr("fluidgrids.executions.executionDetail.agent.iterations", "Iterations"),
6156
- value: trace.iterationCount?.toLocaleString("en-US") ?? "—"
6349
+ value: iterationCount?.toLocaleString("en-US") ?? "—"
6157
6350
  }),
6158
6351
  /* @__PURE__ */ jsx(Fact, {
6159
6352
  label: tr("fluidgrids.executions.executionDetail.agent.tokensUsed", "Tokens used"),
6160
- value: trace.tokensUsed === null ? "—" : formatTokens(trace.tokensUsed)
6353
+ value: tokensUsed === null ? "—" : formatTokens(tokensUsed)
6161
6354
  }),
6162
6355
  /* @__PURE__ */ jsx(Fact, {
6163
6356
  label: tr("fluidgrids.executions.executionDetail.agent.toolCalls", "Tool calls"),
6164
- value: trace.steps.length.toLocaleString("en-US")
6357
+ value: toolCallCount.toLocaleString("en-US")
6165
6358
  }),
6166
6359
  /* @__PURE__ */ jsx(Fact, {
6167
6360
  label: tr("fluidgrids.executions.executionDetail.agent.duration", "Duration"),
@@ -6187,7 +6380,10 @@ function AgentNodeCard({ trace, title, focused, tr }) {
6187
6380
  trace,
6188
6381
  tr
6189
6382
  }),
6190
- /* @__PURE__ */ jsxs("div", {
6383
+ steps ? /* @__PURE__ */ jsx(IterationList, {
6384
+ node: steps,
6385
+ tr
6386
+ }) : /* @__PURE__ */ jsxs("div", {
6191
6387
  className: "mt-4",
6192
6388
  children: [/* @__PURE__ */ jsx("h5", {
6193
6389
  className: "mb-2 text-sm font-medium text-text-primary",
@@ -6318,6 +6514,233 @@ function StepBlock({ label, value, empty }) {
6318
6514
  children: empty
6319
6515
  })] });
6320
6516
  }
6517
+ function StepCost({ value, tr }) {
6518
+ if (value === null) return /* @__PURE__ */ jsx(Fragment, { children: tr("fluidgrids.executions.executionDetail.agent.notPricedYet", "Not priced yet") });
6519
+ return /* @__PURE__ */ jsx(Fragment, { children: formatMicroUsd(value) });
6520
+ }
6521
+ function TokensInOut({ tokensIn, tokensOut, tr }) {
6522
+ return /* @__PURE__ */ jsx(Fragment, { children: tr("fluidgrids.executions.executionDetail.agent.tokensInOut", "{{input}} in / {{output}} out tokens", {
6523
+ input: formatTokens(tokensIn),
6524
+ output: formatTokens(tokensOut)
6525
+ }) });
6526
+ }
6527
+ function IterationList({ node, tr }) {
6528
+ const last = node.iterations.at(-1)?.iteration;
6529
+ return /* @__PURE__ */ jsxs("div", {
6530
+ className: "mt-4",
6531
+ children: [/* @__PURE__ */ jsx("h5", {
6532
+ className: "mb-2 text-sm font-medium text-text-primary",
6533
+ children: tr("fluidgrids.executions.executionDetail.agent.iterations", "Iterations")
6534
+ }), /* @__PURE__ */ jsx("ol", {
6535
+ className: "space-y-2",
6536
+ "data-testid": "agent-iterations",
6537
+ children: node.iterations.map((iteration) => /* @__PURE__ */ jsx(IterationRow, {
6538
+ iteration,
6539
+ open: iteration.iteration === last,
6540
+ tr
6541
+ }, iteration.iteration))
6542
+ })]
6543
+ });
6544
+ }
6545
+ function IterationRow({ iteration, open, tr }) {
6546
+ return /* @__PURE__ */ jsx("li", {
6547
+ className: `rounded border ${iteration.error ? "border-status-error-border" : "border-border-secondary"}`,
6548
+ "data-agent-iteration": iteration.iteration,
6549
+ children: /* @__PURE__ */ jsxs("details", {
6550
+ open,
6551
+ children: [/* @__PURE__ */ jsxs("summary", {
6552
+ className: "flex cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 px-3 py-2 text-sm",
6553
+ children: [
6554
+ /* @__PURE__ */ jsx("span", {
6555
+ className: "font-medium text-text-primary",
6556
+ children: tr("fluidgrids.executions.executionDetail.agent.iterationNumber", "Iteration {{number}}", { number: iteration.iteration + 1 })
6557
+ }),
6558
+ /* @__PURE__ */ jsx("span", {
6559
+ className: "text-xs text-text-secondary",
6560
+ children: /* @__PURE__ */ jsx(TokensInOut, {
6561
+ tokensIn: iteration.tokensIn,
6562
+ tokensOut: iteration.tokensOut,
6563
+ tr
6564
+ })
6565
+ }),
6566
+ /* @__PURE__ */ jsx("span", {
6567
+ className: "text-xs text-text-secondary",
6568
+ children: formatElapsedMs(iteration.latencyMs, "—")
6569
+ }),
6570
+ iteration.turns.length > 0 && /* @__PURE__ */ jsx("span", {
6571
+ className: "ml-auto text-xs text-text-secondary",
6572
+ children: /* @__PURE__ */ jsx(StepCost, {
6573
+ value: iteration.costMicroUsd,
6574
+ tr
6575
+ })
6576
+ })
6577
+ ]
6578
+ }), /* @__PURE__ */ jsxs("div", {
6579
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6580
+ children: [
6581
+ iteration.turns.map((turn) => /* @__PURE__ */ jsx(ModelTurnRow, {
6582
+ turn,
6583
+ tr
6584
+ }, turn.key)),
6585
+ iteration.toolCalls.map((call) => /* @__PURE__ */ jsx(ToolCallRow, {
6586
+ call,
6587
+ tr
6588
+ }, call.key)),
6589
+ iteration.final && /* @__PURE__ */ jsxs("div", {
6590
+ className: "rounded bg-background-tertiary p-2",
6591
+ children: [
6592
+ /* @__PURE__ */ jsx("p", {
6593
+ className: "text-xs font-medium text-text-secondary",
6594
+ children: tr("fluidgrids.executions.executionDetail.agent.finalResponse", "Final response")
6595
+ }),
6596
+ iteration.final.answer && /* @__PURE__ */ jsx("pre", {
6597
+ className: "mt-1 max-h-64 overflow-auto font-mono text-xs whitespace-pre-wrap break-words text-text-primary",
6598
+ children: iteration.final.answer
6599
+ }),
6600
+ iteration.final.outputErrors.length > 0 && /* @__PURE__ */ jsxs("div", {
6601
+ className: "mt-2 text-sm text-status-error-text",
6602
+ children: [/* @__PURE__ */ jsx("p", {
6603
+ className: "font-medium",
6604
+ children: tr("fluidgrids.executions.executionDetail.agent.outputSchemaMissed", "The answer missed the output schema:")
6605
+ }), /* @__PURE__ */ jsx("ul", {
6606
+ className: "mt-1 list-disc pl-5",
6607
+ children: iteration.final.outputErrors.map((error) => /* @__PURE__ */ jsx("li", {
6608
+ className: "break-words",
6609
+ children: error
6610
+ }, error))
6611
+ })]
6612
+ })
6613
+ ]
6614
+ }),
6615
+ iteration.error && /* @__PURE__ */ jsxs("div", {
6616
+ className: "rounded border border-status-error-border bg-status-error-bg-subtle p-2 text-sm text-status-error-text",
6617
+ children: [/* @__PURE__ */ jsxs("span", {
6618
+ className: "font-medium",
6619
+ children: [
6620
+ tr("fluidgrids.executions.executionDetail.agent.error", "Error"),
6621
+ ":",
6622
+ " "
6623
+ ]
6624
+ }), /* @__PURE__ */ jsx("span", {
6625
+ className: "break-words",
6626
+ children: iteration.error
6627
+ })]
6628
+ })
6629
+ ]
6630
+ })]
6631
+ })
6632
+ });
6633
+ }
6634
+ function ModelTurnRow({ turn, tr }) {
6635
+ return /* @__PURE__ */ jsxs("details", {
6636
+ className: "rounded border border-border-secondary",
6637
+ children: [/* @__PURE__ */ jsxs("summary", {
6638
+ className: "flex cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 px-3 py-2 text-sm",
6639
+ children: [
6640
+ /* @__PURE__ */ jsx(Icon, {
6641
+ icon: "mdi:brain",
6642
+ width: 14,
6643
+ className: "shrink-0 text-text-secondary"
6644
+ }),
6645
+ /* @__PURE__ */ jsx("span", {
6646
+ className: "font-medium text-text-primary",
6647
+ children: tr("fluidgrids.executions.executionDetail.agent.modelCall", "Model call")
6648
+ }),
6649
+ turn.model && /* @__PURE__ */ jsx("span", {
6650
+ className: "min-w-0 break-all font-mono text-xs text-text-secondary",
6651
+ children: turn.model
6652
+ }),
6653
+ /* @__PURE__ */ jsx("span", {
6654
+ className: "text-xs text-text-secondary",
6655
+ children: /* @__PURE__ */ jsx(TokensInOut, {
6656
+ tokensIn: turn.tokensIn,
6657
+ tokensOut: turn.tokensOut,
6658
+ tr
6659
+ })
6660
+ }),
6661
+ /* @__PURE__ */ jsx("span", {
6662
+ className: "text-xs text-text-secondary",
6663
+ children: formatElapsedMs(turn.latencyMs, "—")
6664
+ }),
6665
+ /* @__PURE__ */ jsx("span", {
6666
+ className: "ml-auto text-xs text-text-secondary",
6667
+ children: /* @__PURE__ */ jsx(StepCost, {
6668
+ value: turn.costMicroUsd,
6669
+ tr
6670
+ })
6671
+ })
6672
+ ]
6673
+ }), /* @__PURE__ */ jsxs("div", {
6674
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6675
+ children: [/* @__PURE__ */ jsx(StepBlock, {
6676
+ label: tr("fluidgrids.executions.executionDetail.agent.sentToModel", "Sent to the model"),
6677
+ value: turn.input,
6678
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNotRecorded", "Not recorded")
6679
+ }), /* @__PURE__ */ jsx(StepBlock, {
6680
+ label: tr("fluidgrids.executions.executionDetail.agent.modelReply", "Reply"),
6681
+ value: turn.reply,
6682
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNotRecorded", "Not recorded")
6683
+ })]
6684
+ })]
6685
+ });
6686
+ }
6687
+ function ToolCallRow({ call, tr }) {
6688
+ const waiting = !call.hasResult && call.rule === "ask";
6689
+ let border = "border-border-secondary";
6690
+ if (call.outcome === "rejected" || waiting) border = "border-status-warning-border";
6691
+ else if (call.outcome === "failed") border = "border-status-error-border";
6692
+ return /* @__PURE__ */ jsxs("details", {
6693
+ className: `rounded border ${border}`,
6694
+ children: [/* @__PURE__ */ jsxs("summary", {
6695
+ className: "flex cursor-pointer flex-wrap items-center gap-2 px-3 py-2 text-sm",
6696
+ children: [
6697
+ /* @__PURE__ */ jsx(Icon, {
6698
+ icon: "mdi:wrench-outline",
6699
+ width: 14,
6700
+ className: "shrink-0 text-text-secondary"
6701
+ }),
6702
+ /* @__PURE__ */ jsx("span", {
6703
+ className: "min-w-0 break-all font-mono text-text-primary",
6704
+ children: call.tool
6705
+ }),
6706
+ waiting && /* @__PURE__ */ jsx("span", {
6707
+ className: "rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text",
6708
+ children: tr("fluidgrids.executions.executionDetail.agent.stepAwaitingApproval", "Waiting for approval")
6709
+ }),
6710
+ call.outcome === "rejected" && /* @__PURE__ */ jsx("span", {
6711
+ className: "rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text",
6712
+ children: tr("fluidgrids.executions.executionDetail.agent.stepRejected", "Rejected")
6713
+ }),
6714
+ call.outcome === "failed" && /* @__PURE__ */ jsx("span", {
6715
+ className: "rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text",
6716
+ children: tr("fluidgrids.executions.executionDetail.agent.stepFailed", "Failed")
6717
+ }),
6718
+ call.latencyMs !== null && /* @__PURE__ */ jsx("span", {
6719
+ className: "ml-auto text-xs text-text-secondary",
6720
+ children: formatElapsedMs(call.latencyMs)
6721
+ })
6722
+ ]
6723
+ }), /* @__PURE__ */ jsxs("div", {
6724
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6725
+ children: [
6726
+ call.outcome !== "ok" && call.reason && /* @__PURE__ */ jsx("p", {
6727
+ className: `text-sm ${call.outcome === "rejected" ? "text-status-warning-text" : "text-status-error-text"}`,
6728
+ children: call.reason
6729
+ }),
6730
+ /* @__PURE__ */ jsx(StepBlock, {
6731
+ label: tr("fluidgrids.executions.executionDetail.agent.stepArguments", "Arguments"),
6732
+ value: call.args,
6733
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNoArguments", "No arguments")
6734
+ }),
6735
+ /* @__PURE__ */ jsx(StepBlock, {
6736
+ label: tr("fluidgrids.executions.executionDetail.agent.stepResult", "Result"),
6737
+ value: call.result,
6738
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNoResult", "No result recorded")
6739
+ })
6740
+ ]
6741
+ })]
6742
+ });
6743
+ }
6321
6744
 
6322
6745
  // src/modules/executions/hooks/useRunAiUsage.ts
6323
6746
  /**
@@ -6389,6 +6812,145 @@ function useRunAiUsage(runId, { enabled, live }) {
6389
6812
  };
6390
6813
  }
6391
6814
 
6815
+ // src/modules/executions/hooks/useAgentTrace.ts
6816
+ /**
6817
+ * useAgentTrace — the AI Agent step trace of a run (BOFF-9936) for the run
6818
+ * page's Agent tab.
6819
+ *
6820
+ * Two sources feed one list of steps, merged by step `id`:
6821
+ *
6822
+ * read `agentTrace`, the steps stored so far, with each model call's
6823
+ * priced cost.
6824
+ * live `agentStepUpdated`, the steps the worker records after the
6825
+ * subscription opens. It has no history, so the read comes first.
6826
+ *
6827
+ * While `live`, the read is repeated every {@link AGENT_TRACE_POLL_INTERVAL_MS}
6828
+ * until the stream proves it delivers, and once more when the run stops
6829
+ * being live, so the last steps and the costs the ledger priced meanwhile
6830
+ * always arrive. Nothing is read while `enabled` is false (the run has no AI
6831
+ * Agent node, or the tab is not shown).
6832
+ */
6833
+ /** Re-read cadence while the live stream has not delivered yet. */
6834
+ const AGENT_TRACE_POLL_INTERVAL_MS = 5e3;
6835
+ /**
6836
+ * Follow a run's live steps: subscribe to `agentStepUpdated`, and re-read the
6837
+ * stored trace every {@link AGENT_TRACE_POLL_INTERVAL_MS} until the stream
6838
+ * delivers (again after it errors or ends). Returns the function that stops
6839
+ * both; nothing is called back after it ran.
6840
+ */
6841
+ function followAgentSteps(client, runId, { reread, onSteps, onStreaming }) {
6842
+ let stopped = false;
6843
+ let delivered = false;
6844
+ const poll = setInterval(() => {
6845
+ if (!delivered) reread();
6846
+ }, AGENT_TRACE_POLL_INTERVAL_MS);
6847
+ const subscription = client.subscribe({
6848
+ query: AgentStepUpdatedDocument,
6849
+ variables: { runId }
6850
+ }).subscribe({
6851
+ next: ({ data }) => {
6852
+ if (stopped) return;
6853
+ delivered = true;
6854
+ onStreaming(true);
6855
+ const steps = data?.agentStepUpdated ?? [];
6856
+ if (steps.length > 0) onSteps(steps);
6857
+ },
6858
+ error: () => {
6859
+ delivered = false;
6860
+ if (!stopped) onStreaming(false);
6861
+ },
6862
+ complete: () => {
6863
+ delivered = false;
6864
+ if (!stopped) onStreaming(false);
6865
+ }
6866
+ });
6867
+ return () => {
6868
+ stopped = true;
6869
+ clearInterval(poll);
6870
+ subscription.unsubscribe();
6871
+ onStreaming(false);
6872
+ };
6873
+ }
6874
+ function useAgentTrace(runId, { enabled, live }) {
6875
+ const [state, setState] = useState(null);
6876
+ const [streaming, setStreaming] = useState(false);
6877
+ const request = useRef(0);
6878
+ const read = useCallback(async () => {
6879
+ const client = getFluidGridsClientSafe();
6880
+ if (!runId || !enabled || !client) return;
6881
+ const id = ++request.current;
6882
+ try {
6883
+ const result = await client.query({
6884
+ query: AgentTraceDocument,
6885
+ variables: { runId },
6886
+ fetchPolicy: "network-only"
6887
+ });
6888
+ throwIfResultError(result);
6889
+ const trace = result.data?.agentTrace ?? null;
6890
+ if (id !== request.current) return;
6891
+ setState((previous) => ({
6892
+ runId,
6893
+ steps: mergeAgentSteps(previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(), flattenAgentTrace(trace)),
6894
+ dataPruned: trace?.dataPruned ?? false,
6895
+ read: true,
6896
+ error: null
6897
+ }));
6898
+ } catch (err) {
6899
+ if (id !== request.current) return;
6900
+ const message = err instanceof Error ? err.message : String(err);
6901
+ setState((previous) => ({
6902
+ runId,
6903
+ steps: previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(),
6904
+ dataPruned: previous?.runId === runId ? previous.dataPruned : false,
6905
+ read: true,
6906
+ error: message
6907
+ }));
6908
+ }
6909
+ }, [runId, enabled]);
6910
+ useEffect(() => {
6911
+ read();
6912
+ }, [read]);
6913
+ useEffect(() => {
6914
+ const client = getFluidGridsClientSafe();
6915
+ if (!runId || !enabled || !live || !client) return void 0;
6916
+ return followAgentSteps(client, runId, {
6917
+ reread: () => void read(),
6918
+ onStreaming: setStreaming,
6919
+ onSteps: (steps) => setState((previous) => ({
6920
+ runId,
6921
+ steps: mergeAgentSteps(previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(), steps),
6922
+ dataPruned: previous?.runId === runId ? previous.dataPruned : false,
6923
+ read: previous?.runId === runId ? previous.read : false,
6924
+ error: previous?.runId === runId ? previous.error : null
6925
+ }))
6926
+ });
6927
+ }, [
6928
+ runId,
6929
+ enabled,
6930
+ live,
6931
+ read
6932
+ ]);
6933
+ const wasLive = useRef(false);
6934
+ useEffect(() => {
6935
+ if (live) {
6936
+ wasLive.current = true;
6937
+ return;
6938
+ }
6939
+ if (!wasLive.current) return;
6940
+ wasLive.current = false;
6941
+ read();
6942
+ }, [live, read]);
6943
+ const current = enabled && state?.runId === runId ? state : null;
6944
+ return {
6945
+ nodes: useMemo(() => current ? groupAgentSteps(current.steps.values()) : [], [current]),
6946
+ dataPruned: current?.dataPruned ?? false,
6947
+ loading: enabled && !!runId && !current?.read,
6948
+ error: current?.error ?? null,
6949
+ streaming: enabled && live && streaming,
6950
+ refetch: read
6951
+ };
6952
+ }
6953
+
6392
6954
  // src/modules/task-queues/api.ts
6393
6955
  /**
6394
6956
  * Task queues and self-hosted workers (BOFF-9906, spec code-first 04 §8 and
@@ -9096,6 +9658,10 @@ function ExecutionDetailPage() {
9096
9658
  enabled: hasAgent && activeTab === "agent",
9097
9659
  live: autoRefreshEnabled && isActive
9098
9660
  });
9661
+ const agentStepTrace = useAgentTrace(runId, {
9662
+ enabled: hasAgent && activeTab === "agent",
9663
+ live: isActive
9664
+ });
9099
9665
  const agentTraces = useMemo(() => agentNodeTraces(orderNodesByExecution(execution?.nodeStates ?? [], execution?.triggerNodeId ?? void 0)), [execution?.nodeStates, execution?.triggerNodeId]);
9100
9666
  const activityControls = isDurableRun && phaseC.runControlDurable;
9101
9667
  const runPending = useRunPending(runId, {
@@ -9776,7 +10342,10 @@ function ExecutionDetailPage() {
9776
10342
  usageLoading: runAiUsage.loading,
9777
10343
  usageError: runAiUsage.error,
9778
10344
  onRefreshUsage: () => void runAiUsage.refetch(),
9779
- focusNodeId: agentFocusNodeId
10345
+ focusNodeId: agentFocusNodeId,
10346
+ stepTraces: agentStepTrace.nodes,
10347
+ stepTraceDataPruned: agentStepTrace.dataPruned,
10348
+ stepTraceError: agentStepTrace.error
9780
10349
  })]
9781
10350
  }),
9782
10351
  activeTab === "history" && isDurableRun && /* @__PURE__ */ jsxs("div", {