@burdenoff/fluidgrids-fe-sdk 2026.1111.4 → 2026.1111.6

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-wWQocYtw.js} +611 -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,204 @@ 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
+ const error = isRecord$1(output) ? text$1(output.error) : null;
5938
+ return {
5939
+ key: step.id,
5940
+ model: step.model,
5941
+ tokensIn: step.tokensIn,
5942
+ tokensOut: step.tokensOut,
5943
+ latencyMs: step.latencyMs,
5944
+ costMicroUsd: step.costMicroUsd,
5945
+ input: prettyValue(step.input),
5946
+ reply: isRecord$1(output) ? text$1(output.content) ?? prettyValue(output.toolCalls) ?? error : prettyValue(output),
5947
+ metered: step.promptInvocationId !== null,
5948
+ failed: error !== null
5949
+ };
5950
+ }
5951
+ function toFinal(step) {
5952
+ const output = step.output;
5953
+ if (!isRecord$1(output)) return {
5954
+ answer: prettyValue(output),
5955
+ structuredOutput: null,
5956
+ outputErrors: []
5957
+ };
5958
+ return {
5959
+ answer: text$1(output.answer),
5960
+ structuredOutput: prettyValue(output.structuredOutput),
5961
+ outputErrors: Array.isArray(output.outputErrors) ? output.outputErrors.filter((error) => typeof error === "string") : []
5962
+ };
5963
+ }
5964
+ function errorOf(step) {
5965
+ const output = step.output;
5966
+ return isRecord$1(output) ? text$1(output.error) : text$1(output);
5967
+ }
5968
+ function buildIteration(iteration, steps) {
5969
+ const turns = [];
5970
+ const toolCalls = [];
5971
+ const byCallId = /* @__PURE__ */ new Map();
5972
+ let final = null;
5973
+ let error = null;
5974
+ for (const step of steps) switch (step.kind) {
5975
+ case "PLAN":
5976
+ turns.push(toTurn(step));
5977
+ break;
5978
+ case "TOOL_CALL": {
5979
+ const output = step.output;
5980
+ const view = {
5981
+ key: step.id,
5982
+ callId: step.toolCallId,
5983
+ tool: step.toolName ?? "?",
5984
+ args: prettyValue(step.input),
5985
+ rule: isRecord$1(output) ? text$1(output.rule) : null,
5986
+ result: null,
5987
+ hasResult: false,
5988
+ outcome: "ok",
5989
+ reason: null,
5990
+ latencyMs: null,
5991
+ approvalId: null
5992
+ };
5993
+ toolCalls.push(view);
5994
+ if (step.toolCallId) byCallId.set(step.toolCallId, view);
5995
+ break;
5996
+ }
5997
+ case "TOOL_RESULT": {
5998
+ const { outcome, reason } = classifyObservation(step.output);
5999
+ const result = {
6000
+ result: prettyValue(step.output),
6001
+ hasResult: true,
6002
+ outcome,
6003
+ reason,
6004
+ latencyMs: step.latencyMs,
6005
+ approvalId: step.approvalId
6006
+ };
6007
+ const call = step.toolCallId ? byCallId.get(step.toolCallId) : void 0;
6008
+ if (call) Object.assign(call, result);
6009
+ else toolCalls.push({
6010
+ key: step.id,
6011
+ callId: step.toolCallId,
6012
+ tool: step.toolName ?? "?",
6013
+ args: null,
6014
+ rule: null,
6015
+ ...result
6016
+ });
6017
+ break;
6018
+ }
6019
+ case "FINAL":
6020
+ final = toFinal(step);
6021
+ break;
6022
+ case "ERROR": error = errorOf(step);
6023
+ }
6024
+ return {
6025
+ iteration,
6026
+ turns,
6027
+ toolCalls,
6028
+ final,
6029
+ error,
6030
+ tokensIn: turns.reduce((sum, turn) => sum + (turn.tokensIn ?? 0), 0),
6031
+ tokensOut: turns.reduce((sum, turn) => sum + (turn.tokensOut ?? 0), 0),
6032
+ latencyMs: steps.reduce((sum, step) => sum + (step.latencyMs ?? 0), 0),
6033
+ costMicroUsd: sumCost(turns.map((turn) => turn.costMicroUsd)),
6034
+ hasTokens: turns.some((turn) => turn.tokensIn !== null || turn.tokensOut !== null),
6035
+ metered: turns.some((turn) => turn.metered)
6036
+ };
6037
+ }
6038
+ function nodeKey(step) {
6039
+ return step.nodeStateId ?? `node:${step.nodeId}`;
6040
+ }
6041
+ /**
6042
+ * The steps grouped by node execution (in the order the executions started)
6043
+ * and iteration. A tool result recorded after an approval pause belongs to
6044
+ * the iteration of its call.
6045
+ */
6046
+ function groupAgentSteps(steps) {
6047
+ const byNode = /* @__PURE__ */ new Map();
6048
+ for (const step of steps) {
6049
+ const key = nodeKey(step);
6050
+ const list = byNode.get(key);
6051
+ if (list) list.push(step);
6052
+ else byNode.set(key, [step]);
6053
+ }
6054
+ const nodes = [];
6055
+ for (const list of byNode.values()) {
6056
+ list.sort((a, b) => a.sequence - b.sequence);
6057
+ const byIteration = /* @__PURE__ */ new Map();
6058
+ for (const step of list) {
6059
+ const group = byIteration.get(step.iteration);
6060
+ if (group) group.push(step);
6061
+ else byIteration.set(step.iteration, [step]);
6062
+ }
6063
+ const iterations = [...byIteration.entries()].sort(([a], [b]) => a - b).map(([iteration, group]) => buildIteration(iteration, group));
6064
+ const first = list[0];
6065
+ if (!first) continue;
6066
+ nodes.push({
6067
+ nodeId: first.nodeId,
6068
+ nodeStateId: first.nodeStateId,
6069
+ iterations,
6070
+ totals: {
6071
+ iterations: iterations.length,
6072
+ modelCalls: iterations.reduce((sum, it) => sum + it.turns.length, 0),
6073
+ toolCalls: iterations.reduce((sum, it) => sum + it.toolCalls.length, 0),
6074
+ tokensIn: iterations.reduce((sum, it) => sum + it.tokensIn, 0),
6075
+ tokensOut: iterations.reduce((sum, it) => sum + it.tokensOut, 0),
6076
+ costMicroUsd: sumCost(iterations.map((it) => it.costMicroUsd))
6077
+ },
6078
+ startedAt: list.reduce((earliest, step) => step.createdAt < earliest ? step.createdAt : earliest, first.createdAt)
6079
+ });
6080
+ }
6081
+ return nodes.sort((a, b) => a.startedAt.localeCompare(b.startedAt)).map(({ startedAt: _startedAt, ...node }) => node);
6082
+ }
6083
+ /** The step trace of one node execution, when the run recorded one. */
6084
+ function stepTraceFor(nodes, nodeStateId, nodeId) {
6085
+ return nodes.find((node) => node.nodeStateId === nodeStateId) ?? nodes.find((node) => node.nodeStateId === null && node.nodeId === nodeId) ?? null;
6086
+ }
6087
+
5906
6088
  // src/modules/executions/components/AgentTracePanel.tsx
5907
6089
  /**
5908
6090
  * The run page's Agent tab (BOFF-9937): the run's AI usage roll-up and, per
5909
6091
  * AI Agent node, what the agent did — its tool calls in order, the final
5910
6092
  * response, and the calls waiting for a person's approval.
5911
6093
  *
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.
6094
+ * Read-only. The roll-up comes from `runAiUsage`. The per-node trace comes
6095
+ * from the step trace (`agentTrace`, BOFF-9936; see utils/agent-steps.ts):
6096
+ * each iteration's model calls with their tokens, timing and cost, what was
6097
+ * sent to the model and its reply, the tool calls and the final answer. A run
6098
+ * without a step trace (recorded before it existed) falls back to the node
6099
+ * states the run page already polls (see utils/agent-trace.ts).
5916
6100
  */
5917
- function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError, onRefreshUsage, focusNodeId }) {
6101
+ function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError, onRefreshUsage, focusNodeId, stepTraces = [], stepTraceDataPruned = false, stepTraceError = null }) {
5918
6102
  const tr = useTr();
6103
+ const anyStepTrace = traces.some((trace) => !!stepTraceFor(stepTraces, trace.nodeStateId, trace.nodeId));
5919
6104
  return /* @__PURE__ */ jsxs("div", {
5920
6105
  className: "space-y-6",
5921
6106
  "data-testid": "agent-trace-panel",
@@ -5928,15 +6113,25 @@ function AgentTracePanel({ traces, nodeDisplay, usage, usageLoading, usageError,
5928
6113
  nodeDisplay,
5929
6114
  tr
5930
6115
  }),
6116
+ stepTraceError && /* @__PURE__ */ jsx("div", {
6117
+ role: "alert",
6118
+ className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
6119
+ children: tr("fluidgrids.executions.executionDetail.agent.stepTraceError", "Could not load the agent's steps: {{message}}", { message: stepTraceError })
6120
+ }),
6121
+ anyStepTrace && stepTraceDataPruned && /* @__PURE__ */ jsx("p", {
6122
+ className: "text-sm text-text-secondary",
6123
+ 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.")
6124
+ }),
5931
6125
  traces.map((trace) => /* @__PURE__ */ jsx(AgentNodeCard, {
5932
6126
  trace,
6127
+ steps: stepTraceFor(stepTraces, trace.nodeStateId, trace.nodeId),
5933
6128
  title: nodeDisplayTitle(nodeDisplay[trace.nodeId], trace.nodeId),
5934
6129
  focused: !!focusNodeId && focusNodeId === trace.nodeId,
5935
6130
  tr
5936
6131
  }, trace.nodeStateId)),
5937
- /* @__PURE__ */ jsx("p", {
6132
+ !anyStepTrace && /* @__PURE__ */ jsx("p", {
5938
6133
  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.")
6134
+ 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
6135
  })
5941
6136
  ]
5942
6137
  });
@@ -6109,7 +6304,10 @@ function UsageLines({ lines, nodeDisplay, tr }) {
6109
6304
  })
6110
6305
  });
6111
6306
  }
6112
- function AgentNodeCard({ trace, title, focused, tr }) {
6307
+ function AgentNodeCard({ trace, steps, title, focused, tr }) {
6308
+ const iterationCount = steps?.totals.iterations ?? trace.iterationCount;
6309
+ const tokensUsed = steps ? steps.totals.tokensIn + steps.totals.tokensOut : trace.tokensUsed;
6310
+ const toolCallCount = steps?.totals.toolCalls ?? trace.steps.length;
6113
6311
  const ref = useRef(null);
6114
6312
  useEffect(() => {
6115
6313
  if (focused) ref.current?.scrollIntoView({ block: "start" });
@@ -6153,15 +6351,15 @@ function AgentNodeCard({ trace, title, focused, tr }) {
6153
6351
  children: [
6154
6352
  /* @__PURE__ */ jsx(Fact, {
6155
6353
  label: tr("fluidgrids.executions.executionDetail.agent.iterations", "Iterations"),
6156
- value: trace.iterationCount?.toLocaleString("en-US") ?? "—"
6354
+ value: iterationCount?.toLocaleString("en-US") ?? "—"
6157
6355
  }),
6158
6356
  /* @__PURE__ */ jsx(Fact, {
6159
6357
  label: tr("fluidgrids.executions.executionDetail.agent.tokensUsed", "Tokens used"),
6160
- value: trace.tokensUsed === null ? "—" : formatTokens(trace.tokensUsed)
6358
+ value: tokensUsed === null ? "—" : formatTokens(tokensUsed)
6161
6359
  }),
6162
6360
  /* @__PURE__ */ jsx(Fact, {
6163
6361
  label: tr("fluidgrids.executions.executionDetail.agent.toolCalls", "Tool calls"),
6164
- value: trace.steps.length.toLocaleString("en-US")
6362
+ value: toolCallCount.toLocaleString("en-US")
6165
6363
  }),
6166
6364
  /* @__PURE__ */ jsx(Fact, {
6167
6365
  label: tr("fluidgrids.executions.executionDetail.agent.duration", "Duration"),
@@ -6187,7 +6385,10 @@ function AgentNodeCard({ trace, title, focused, tr }) {
6187
6385
  trace,
6188
6386
  tr
6189
6387
  }),
6190
- /* @__PURE__ */ jsxs("div", {
6388
+ steps ? /* @__PURE__ */ jsx(IterationList, {
6389
+ node: steps,
6390
+ tr
6391
+ }) : /* @__PURE__ */ jsxs("div", {
6191
6392
  className: "mt-4",
6192
6393
  children: [/* @__PURE__ */ jsx("h5", {
6193
6394
  className: "mb-2 text-sm font-medium text-text-primary",
@@ -6318,6 +6519,237 @@ function StepBlock({ label, value, empty }) {
6318
6519
  children: empty
6319
6520
  })] });
6320
6521
  }
6522
+ function StepCost({ value, tr }) {
6523
+ if (value === null) return /* @__PURE__ */ jsx(Fragment, { children: tr("fluidgrids.executions.executionDetail.agent.notPricedYet", "Not priced yet") });
6524
+ return /* @__PURE__ */ jsx(Fragment, { children: formatMicroUsd(value) });
6525
+ }
6526
+ function TokensInOut({ tokensIn, tokensOut, tr }) {
6527
+ return /* @__PURE__ */ jsx(Fragment, { children: tr("fluidgrids.executions.executionDetail.agent.tokensInOut", "{{input}} in / {{output}} out tokens", {
6528
+ input: formatTokens(tokensIn),
6529
+ output: formatTokens(tokensOut)
6530
+ }) });
6531
+ }
6532
+ function IterationList({ node, tr }) {
6533
+ const last = node.iterations.at(-1)?.iteration;
6534
+ return /* @__PURE__ */ jsxs("div", {
6535
+ className: "mt-4",
6536
+ children: [/* @__PURE__ */ jsx("h5", {
6537
+ className: "mb-2 text-sm font-medium text-text-primary",
6538
+ children: tr("fluidgrids.executions.executionDetail.agent.iterations", "Iterations")
6539
+ }), /* @__PURE__ */ jsx("ol", {
6540
+ className: "space-y-2",
6541
+ "data-testid": "agent-iterations",
6542
+ children: node.iterations.map((iteration) => /* @__PURE__ */ jsx(IterationRow, {
6543
+ iteration,
6544
+ open: iteration.iteration === last,
6545
+ tr
6546
+ }, iteration.iteration))
6547
+ })]
6548
+ });
6549
+ }
6550
+ function IterationRow({ iteration, open, tr }) {
6551
+ return /* @__PURE__ */ jsx("li", {
6552
+ className: `rounded border ${iteration.error ? "border-status-error-border" : "border-border-secondary"}`,
6553
+ "data-agent-iteration": iteration.iteration,
6554
+ children: /* @__PURE__ */ jsxs("details", {
6555
+ open,
6556
+ children: [/* @__PURE__ */ jsxs("summary", {
6557
+ className: "flex cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 px-3 py-2 text-sm",
6558
+ children: [
6559
+ /* @__PURE__ */ jsx("span", {
6560
+ className: "font-medium text-text-primary",
6561
+ children: tr("fluidgrids.executions.executionDetail.agent.iterationNumber", "Iteration {{number}}", { number: iteration.iteration + 1 })
6562
+ }),
6563
+ iteration.hasTokens && /* @__PURE__ */ jsx("span", {
6564
+ className: "text-xs text-text-secondary",
6565
+ children: /* @__PURE__ */ jsx(TokensInOut, {
6566
+ tokensIn: iteration.tokensIn,
6567
+ tokensOut: iteration.tokensOut,
6568
+ tr
6569
+ })
6570
+ }),
6571
+ /* @__PURE__ */ jsx("span", {
6572
+ className: "text-xs text-text-secondary",
6573
+ children: formatElapsedMs(iteration.latencyMs, "—")
6574
+ }),
6575
+ iteration.metered && /* @__PURE__ */ jsx("span", {
6576
+ className: "ml-auto text-xs text-text-secondary",
6577
+ children: /* @__PURE__ */ jsx(StepCost, {
6578
+ value: iteration.costMicroUsd,
6579
+ tr
6580
+ })
6581
+ })
6582
+ ]
6583
+ }), /* @__PURE__ */ jsxs("div", {
6584
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6585
+ children: [
6586
+ iteration.turns.map((turn) => /* @__PURE__ */ jsx(ModelTurnRow, {
6587
+ turn,
6588
+ tr
6589
+ }, turn.key)),
6590
+ iteration.toolCalls.map((call) => /* @__PURE__ */ jsx(ToolCallRow, {
6591
+ call,
6592
+ tr
6593
+ }, call.key)),
6594
+ iteration.final && /* @__PURE__ */ jsxs("div", {
6595
+ className: "rounded bg-background-tertiary p-2",
6596
+ children: [
6597
+ /* @__PURE__ */ jsx("p", {
6598
+ className: "text-xs font-medium text-text-secondary",
6599
+ children: tr("fluidgrids.executions.executionDetail.agent.finalResponse", "Final response")
6600
+ }),
6601
+ iteration.final.answer && /* @__PURE__ */ jsx("pre", {
6602
+ className: "mt-1 max-h-64 overflow-auto font-mono text-xs whitespace-pre-wrap break-words text-text-primary",
6603
+ children: iteration.final.answer
6604
+ }),
6605
+ iteration.final.outputErrors.length > 0 && /* @__PURE__ */ jsxs("div", {
6606
+ className: "mt-2 text-sm text-status-error-text",
6607
+ children: [/* @__PURE__ */ jsx("p", {
6608
+ className: "font-medium",
6609
+ children: tr("fluidgrids.executions.executionDetail.agent.outputSchemaMissed", "The answer missed the output schema:")
6610
+ }), /* @__PURE__ */ jsx("ul", {
6611
+ className: "mt-1 list-disc pl-5",
6612
+ children: iteration.final.outputErrors.map((error) => /* @__PURE__ */ jsx("li", {
6613
+ className: "break-words",
6614
+ children: error
6615
+ }, error))
6616
+ })]
6617
+ })
6618
+ ]
6619
+ }),
6620
+ iteration.error && /* @__PURE__ */ jsxs("div", {
6621
+ className: "rounded border border-status-error-border bg-status-error-bg-subtle p-2 text-sm text-status-error-text",
6622
+ children: [/* @__PURE__ */ jsxs("span", {
6623
+ className: "font-medium",
6624
+ children: [
6625
+ tr("fluidgrids.executions.executionDetail.agent.error", "Error"),
6626
+ ":",
6627
+ " "
6628
+ ]
6629
+ }), /* @__PURE__ */ jsx("span", {
6630
+ className: "break-words",
6631
+ children: iteration.error
6632
+ })]
6633
+ })
6634
+ ]
6635
+ })]
6636
+ })
6637
+ });
6638
+ }
6639
+ function ModelTurnRow({ turn, tr }) {
6640
+ return /* @__PURE__ */ jsxs("details", {
6641
+ className: "rounded border border-border-secondary",
6642
+ children: [/* @__PURE__ */ jsxs("summary", {
6643
+ className: "flex cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 px-3 py-2 text-sm",
6644
+ children: [
6645
+ /* @__PURE__ */ jsx(Icon, {
6646
+ icon: "mdi:brain",
6647
+ width: 14,
6648
+ className: "shrink-0 text-text-secondary"
6649
+ }),
6650
+ /* @__PURE__ */ jsx("span", {
6651
+ className: "font-medium text-text-primary",
6652
+ children: tr("fluidgrids.executions.executionDetail.agent.modelCall", "Model call")
6653
+ }),
6654
+ turn.model && /* @__PURE__ */ jsx("span", {
6655
+ className: "min-w-0 break-all font-mono text-xs text-text-secondary",
6656
+ children: turn.model
6657
+ }),
6658
+ turn.failed && /* @__PURE__ */ jsx("span", {
6659
+ className: "rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text",
6660
+ children: tr("fluidgrids.executions.executionDetail.agent.stepFailed", "Failed")
6661
+ }),
6662
+ (turn.tokensIn !== null || turn.tokensOut !== null) && /* @__PURE__ */ jsx("span", {
6663
+ className: "text-xs text-text-secondary",
6664
+ children: /* @__PURE__ */ jsx(TokensInOut, {
6665
+ tokensIn: turn.tokensIn,
6666
+ tokensOut: turn.tokensOut,
6667
+ tr
6668
+ })
6669
+ }),
6670
+ /* @__PURE__ */ jsx("span", {
6671
+ className: "text-xs text-text-secondary",
6672
+ children: formatElapsedMs(turn.latencyMs, "—")
6673
+ }),
6674
+ turn.metered && /* @__PURE__ */ jsx("span", {
6675
+ className: "ml-auto text-xs text-text-secondary",
6676
+ children: /* @__PURE__ */ jsx(StepCost, {
6677
+ value: turn.costMicroUsd,
6678
+ tr
6679
+ })
6680
+ })
6681
+ ]
6682
+ }), /* @__PURE__ */ jsxs("div", {
6683
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6684
+ children: [/* @__PURE__ */ jsx(StepBlock, {
6685
+ label: tr("fluidgrids.executions.executionDetail.agent.sentToModel", "Sent to the model"),
6686
+ value: turn.input,
6687
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNotRecorded", "Not recorded")
6688
+ }), /* @__PURE__ */ jsx(StepBlock, {
6689
+ label: tr("fluidgrids.executions.executionDetail.agent.modelReply", "Reply"),
6690
+ value: turn.reply,
6691
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNotRecorded", "Not recorded")
6692
+ })]
6693
+ })]
6694
+ });
6695
+ }
6696
+ function ToolCallRow({ call, tr }) {
6697
+ const waiting = !call.hasResult && call.rule === "ask";
6698
+ let border = "border-border-secondary";
6699
+ if (call.outcome === "rejected" || waiting) border = "border-status-warning-border";
6700
+ else if (call.outcome === "failed") border = "border-status-error-border";
6701
+ return /* @__PURE__ */ jsxs("details", {
6702
+ className: `rounded border ${border}`,
6703
+ children: [/* @__PURE__ */ jsxs("summary", {
6704
+ className: "flex cursor-pointer flex-wrap items-center gap-2 px-3 py-2 text-sm",
6705
+ children: [
6706
+ /* @__PURE__ */ jsx(Icon, {
6707
+ icon: "mdi:wrench-outline",
6708
+ width: 14,
6709
+ className: "shrink-0 text-text-secondary"
6710
+ }),
6711
+ /* @__PURE__ */ jsx("span", {
6712
+ className: "min-w-0 break-all font-mono text-text-primary",
6713
+ children: call.tool
6714
+ }),
6715
+ waiting && /* @__PURE__ */ jsx("span", {
6716
+ className: "rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text",
6717
+ children: tr("fluidgrids.executions.executionDetail.agent.stepAwaitingApproval", "Waiting for approval")
6718
+ }),
6719
+ call.outcome === "rejected" && /* @__PURE__ */ jsx("span", {
6720
+ className: "rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text",
6721
+ children: tr("fluidgrids.executions.executionDetail.agent.stepRejected", "Rejected")
6722
+ }),
6723
+ call.outcome === "failed" && /* @__PURE__ */ jsx("span", {
6724
+ className: "rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text",
6725
+ children: tr("fluidgrids.executions.executionDetail.agent.stepFailed", "Failed")
6726
+ }),
6727
+ call.latencyMs !== null && /* @__PURE__ */ jsx("span", {
6728
+ className: "ml-auto text-xs text-text-secondary",
6729
+ children: formatElapsedMs(call.latencyMs)
6730
+ })
6731
+ ]
6732
+ }), /* @__PURE__ */ jsxs("div", {
6733
+ className: "space-y-2 border-t border-border-secondary px-3 py-2",
6734
+ children: [
6735
+ call.outcome !== "ok" && call.reason && /* @__PURE__ */ jsx("p", {
6736
+ className: `text-sm ${call.outcome === "rejected" ? "text-status-warning-text" : "text-status-error-text"}`,
6737
+ children: call.reason
6738
+ }),
6739
+ /* @__PURE__ */ jsx(StepBlock, {
6740
+ label: tr("fluidgrids.executions.executionDetail.agent.stepArguments", "Arguments"),
6741
+ value: call.args,
6742
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNoArguments", "No arguments")
6743
+ }),
6744
+ /* @__PURE__ */ jsx(StepBlock, {
6745
+ label: tr("fluidgrids.executions.executionDetail.agent.stepResult", "Result"),
6746
+ value: call.result,
6747
+ empty: tr("fluidgrids.executions.executionDetail.agent.stepNoResult", "No result recorded")
6748
+ })
6749
+ ]
6750
+ })]
6751
+ });
6752
+ }
6321
6753
 
6322
6754
  // src/modules/executions/hooks/useRunAiUsage.ts
6323
6755
  /**
@@ -6389,6 +6821,145 @@ function useRunAiUsage(runId, { enabled, live }) {
6389
6821
  };
6390
6822
  }
6391
6823
 
6824
+ // src/modules/executions/hooks/useAgentTrace.ts
6825
+ /**
6826
+ * useAgentTrace — the AI Agent step trace of a run (BOFF-9936) for the run
6827
+ * page's Agent tab.
6828
+ *
6829
+ * Two sources feed one list of steps, merged by step `id`:
6830
+ *
6831
+ * read `agentTrace`, the steps stored so far, with each model call's
6832
+ * priced cost.
6833
+ * live `agentStepUpdated`, the steps the worker records after the
6834
+ * subscription opens. It has no history, so the read comes first.
6835
+ *
6836
+ * While `live`, the read is repeated every {@link AGENT_TRACE_POLL_INTERVAL_MS}
6837
+ * until the stream proves it delivers, and once more when the run stops
6838
+ * being live, so the last steps and the costs the ledger priced meanwhile
6839
+ * always arrive. Nothing is read while `enabled` is false (the run has no AI
6840
+ * Agent node, or the tab is not shown).
6841
+ */
6842
+ /** Re-read cadence while the live stream has not delivered yet. */
6843
+ const AGENT_TRACE_POLL_INTERVAL_MS = 5e3;
6844
+ /**
6845
+ * Follow a run's live steps: subscribe to `agentStepUpdated`, and re-read the
6846
+ * stored trace every {@link AGENT_TRACE_POLL_INTERVAL_MS} until the stream
6847
+ * delivers (again after it errors or ends). Returns the function that stops
6848
+ * both; nothing is called back after it ran.
6849
+ */
6850
+ function followAgentSteps(client, runId, { reread, onSteps, onStreaming }) {
6851
+ let stopped = false;
6852
+ let delivered = false;
6853
+ const poll = setInterval(() => {
6854
+ if (!delivered) reread();
6855
+ }, AGENT_TRACE_POLL_INTERVAL_MS);
6856
+ const subscription = client.subscribe({
6857
+ query: AgentStepUpdatedDocument,
6858
+ variables: { runId }
6859
+ }).subscribe({
6860
+ next: ({ data }) => {
6861
+ if (stopped) return;
6862
+ delivered = true;
6863
+ onStreaming(true);
6864
+ const steps = data?.agentStepUpdated ?? [];
6865
+ if (steps.length > 0) onSteps(steps);
6866
+ },
6867
+ error: () => {
6868
+ delivered = false;
6869
+ if (!stopped) onStreaming(false);
6870
+ },
6871
+ complete: () => {
6872
+ delivered = false;
6873
+ if (!stopped) onStreaming(false);
6874
+ }
6875
+ });
6876
+ return () => {
6877
+ stopped = true;
6878
+ clearInterval(poll);
6879
+ subscription.unsubscribe();
6880
+ onStreaming(false);
6881
+ };
6882
+ }
6883
+ function useAgentTrace(runId, { enabled, live }) {
6884
+ const [state, setState] = useState(null);
6885
+ const [streaming, setStreaming] = useState(false);
6886
+ const request = useRef(0);
6887
+ const read = useCallback(async () => {
6888
+ const client = getFluidGridsClientSafe();
6889
+ if (!runId || !enabled || !client) return;
6890
+ const id = ++request.current;
6891
+ try {
6892
+ const result = await client.query({
6893
+ query: AgentTraceDocument,
6894
+ variables: { runId },
6895
+ fetchPolicy: "network-only"
6896
+ });
6897
+ throwIfResultError(result);
6898
+ const trace = result.data?.agentTrace ?? null;
6899
+ if (id !== request.current) return;
6900
+ setState((previous) => ({
6901
+ runId,
6902
+ steps: mergeAgentSteps(previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(), flattenAgentTrace(trace)),
6903
+ dataPruned: trace?.dataPruned ?? false,
6904
+ read: true,
6905
+ error: null
6906
+ }));
6907
+ } catch (err) {
6908
+ if (id !== request.current) return;
6909
+ const message = err instanceof Error ? err.message : String(err);
6910
+ setState((previous) => ({
6911
+ runId,
6912
+ steps: previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(),
6913
+ dataPruned: previous?.runId === runId ? previous.dataPruned : false,
6914
+ read: true,
6915
+ error: message
6916
+ }));
6917
+ }
6918
+ }, [runId, enabled]);
6919
+ useEffect(() => {
6920
+ read();
6921
+ }, [read]);
6922
+ useEffect(() => {
6923
+ const client = getFluidGridsClientSafe();
6924
+ if (!runId || !enabled || !live || !client) return void 0;
6925
+ return followAgentSteps(client, runId, {
6926
+ reread: () => void read(),
6927
+ onStreaming: setStreaming,
6928
+ onSteps: (steps) => setState((previous) => ({
6929
+ runId,
6930
+ steps: mergeAgentSteps(previous?.runId === runId ? previous.steps : /* @__PURE__ */ new Map(), steps),
6931
+ dataPruned: previous?.runId === runId ? previous.dataPruned : false,
6932
+ read: previous?.runId === runId ? previous.read : false,
6933
+ error: previous?.runId === runId ? previous.error : null
6934
+ }))
6935
+ });
6936
+ }, [
6937
+ runId,
6938
+ enabled,
6939
+ live,
6940
+ read
6941
+ ]);
6942
+ const wasLive = useRef(false);
6943
+ useEffect(() => {
6944
+ if (live) {
6945
+ wasLive.current = true;
6946
+ return;
6947
+ }
6948
+ if (!wasLive.current) return;
6949
+ wasLive.current = false;
6950
+ read();
6951
+ }, [live, read]);
6952
+ const current = enabled && state?.runId === runId ? state : null;
6953
+ return {
6954
+ nodes: useMemo(() => current ? groupAgentSteps(current.steps.values()) : [], [current]),
6955
+ dataPruned: current?.dataPruned ?? false,
6956
+ loading: enabled && !!runId && !current?.read,
6957
+ error: current?.error ?? null,
6958
+ streaming: enabled && live && streaming,
6959
+ refetch: read
6960
+ };
6961
+ }
6962
+
6392
6963
  // src/modules/task-queues/api.ts
6393
6964
  /**
6394
6965
  * Task queues and self-hosted workers (BOFF-9906, spec code-first 04 §8 and
@@ -9096,6 +9667,10 @@ function ExecutionDetailPage() {
9096
9667
  enabled: hasAgent && activeTab === "agent",
9097
9668
  live: autoRefreshEnabled && isActive
9098
9669
  });
9670
+ const agentStepTrace = useAgentTrace(runId, {
9671
+ enabled: hasAgent && activeTab === "agent",
9672
+ live: isActive
9673
+ });
9099
9674
  const agentTraces = useMemo(() => agentNodeTraces(orderNodesByExecution(execution?.nodeStates ?? [], execution?.triggerNodeId ?? void 0)), [execution?.nodeStates, execution?.triggerNodeId]);
9100
9675
  const activityControls = isDurableRun && phaseC.runControlDurable;
9101
9676
  const runPending = useRunPending(runId, {
@@ -9776,7 +10351,10 @@ function ExecutionDetailPage() {
9776
10351
  usageLoading: runAiUsage.loading,
9777
10352
  usageError: runAiUsage.error,
9778
10353
  onRefreshUsage: () => void runAiUsage.refetch(),
9779
- focusNodeId: agentFocusNodeId
10354
+ focusNodeId: agentFocusNodeId,
10355
+ stepTraces: agentStepTrace.nodes,
10356
+ stepTraceDataPruned: agentStepTrace.dataPruned,
10357
+ stepTraceError: agentStepTrace.error
9780
10358
  })]
9781
10359
  }),
9782
10360
  activeTab === "history" && isDurableRun && /* @__PURE__ */ jsxs("div", {