@@ -694,8 +695,8 @@ export function TestcasesTableShell(props: TestcasesTableShellProps) {
width: 56,
fixed: "right",
align: "center",
- columnVisibilityLocked: true as any,
- exportEnabled: false as any, // Exclude from client-side CSV export
+ columnVisibilityLocked: true,
+ exportEnabled: false, // Exclude from client-side CSV export
onCell: () => ({className: "ag-table-actions-cell"}),
render: (_, record) => {
if (record.__isSkeleton || isShowingSkeleton) return null
diff --git a/web/oss/src/components/TestcasesTableNew/hooks/useTestcasesTable.ts b/web/oss/src/components/TestcasesTableNew/hooks/useTestcasesTable.ts
index dc3a20ffa3..9a956a5e5e 100644
--- a/web/oss/src/components/TestcasesTableNew/hooks/useTestcasesTable.ts
+++ b/web/oss/src/components/TestcasesTableNew/hooks/useTestcasesTable.ts
@@ -205,11 +205,12 @@ export function useTestcasesTable(options: UseTestcasesTableOptions = {}): UseTe
// 1. flags.has_testcases - explicit boolean from backend
// 2. data.testcases array - inline testcases (when include_testcases=true)
// 3. data.testcase_ids array - list of testcase references
- const revisionHasColumnData =
+ const revisionHasColumnData = Boolean(
revisionData &&
(revisionData.flags?.has_testcases === true ||
(revisionData.data?.testcases && revisionData.data.testcases.length > 0) ||
- (revisionData.data?.testcase_ids && revisionData.data.testcase_ids.length > 0))
+ (revisionData.data?.testcase_ids && revisionData.data.testcase_ids.length > 0)),
+ )
const stillDerivingColumns = columns.length === 0 && revisionHasColumnData
// Combined loading state - true when any data source is still loading
diff --git a/web/oss/src/components/TestsetsTable/components/TestsetsHeaderFilters.tsx b/web/oss/src/components/TestsetsTable/components/TestsetsHeaderFilters.tsx
index 75e68e4e1b..fab004c71e 100644
--- a/web/oss/src/components/TestsetsTable/components/TestsetsHeaderFilters.tsx
+++ b/web/oss/src/components/TestsetsTable/components/TestsetsHeaderFilters.tsx
@@ -1,6 +1,6 @@
import {useCallback, useState} from "react"
-import {Input} from "antd"
+import {Input, type PopoverProps} from "antd"
import {useAtom} from "jotai"
import {FiltersPopoverTrigger} from "@/oss/components/InfiniteVirtualTable"
@@ -46,6 +46,8 @@ const TestsetsHeaderFilters = ({tableMode = "active"}: TestsetsHeaderFiltersProp
padding: 0,
},
arrow: false,
+ // antd v6 dropped the `body` semantic key (now container/content), so the
+ // `body` styles are ignored at runtime — typed as-is per WP-4e-2a.
styles: {
body: {
maxWidth: "360px",
@@ -53,7 +55,7 @@ const TestsetsHeaderFilters = ({tableMode = "active"}: TestsetsHeaderFiltersProp
boxShadow: "none",
border: "none",
},
- },
+ } as PopoverProps["styles"],
}}
renderContent={(close) => (
diff --git a/web/oss/src/components/TestsetsTable/hooks/useTestsetsColumns.tsx b/web/oss/src/components/TestsetsTable/hooks/useTestsetsColumns.tsx
index e72b77355d..89533a8609 100644
--- a/web/oss/src/components/TestsetsTable/hooks/useTestsetsColumns.tsx
+++ b/web/oss/src/components/TestsetsTable/hooks/useTestsetsColumns.tsx
@@ -111,7 +111,8 @@ export const useTestsetsColumns = ({
icon:
,
onClick: (e) => {
e.domEvent.stopPropagation()
- copyToClipboard(record._id)
+ // Latent bug kept as-is: `_id` is not a typed TestsetTableRow field (row id is `id`)
+ copyToClipboard(String(record._id ?? ""))
},
},
...(record.slug
diff --git a/web/oss/src/components/VariantsComponents/store/registryStore.ts b/web/oss/src/components/VariantsComponents/store/registryStore.ts
index 9978730cc3..0d2ca93fb6 100644
--- a/web/oss/src/components/VariantsComponents/store/registryStore.ts
+++ b/web/oss/src/components/VariantsComponents/store/registryStore.ts
@@ -6,9 +6,9 @@
*/
import {createPaginatedEntityStore} from "@agenta/entities/shared"
-import type {InfiniteTableFetchResult, WindowingState} from "@agenta/entities/shared"
+import type {InfiniteTableFetchResult} from "@agenta/entities/shared"
import {queryWorkflowRevisionsByWorkflow, queryWorkflowVariants} from "@agenta/entities/workflow"
-import type {Workflow} from "@agenta/entities/workflow"
+import type {Workflow, WorkflowRevisionWindowing} from "@agenta/entities/workflow"
import {projectIdAtom} from "@agenta/shared/state"
import {atom} from "jotai"
@@ -170,7 +170,7 @@ export const registryPaginatedStore = createPaginatedEntityStore<
_variantNameCache = {workflowId: meta.workflowId, map}
}
- const windowing: WindowingState = {
+ const windowing: WorkflowRevisionWindowing = {
next: cursor,
limit,
order: "descending",
diff --git a/web/oss/src/components/Webhooks/WebhookLogsTab.tsx b/web/oss/src/components/Webhooks/WebhookLogsTab.tsx
index 9cea4a8419..afdb2464d3 100644
--- a/web/oss/src/components/Webhooks/WebhookLogsTab.tsx
+++ b/web/oss/src/components/Webhooks/WebhookLogsTab.tsx
@@ -156,6 +156,7 @@ export const WebhookLogsTab = ({subscriptionId}: {subscriptionId: string}) => {
{
- const {
- provider,
- url,
- auth_mode,
- auth_value,
- github_sub_type,
- github_repo,
- github_pat,
- github_workflow,
- github_branch,
- } = formValues
-
// Form stores headers as header_list (array of {key, value}), convert to Record
const headerList = (formValues as any).header_list as {key: string; value: string}[] | undefined
const customHeaders: Record = {}
@@ -211,7 +199,8 @@ export const buildPreviewRequest = (
...authHeader,
})
- if (provider === "webhook") {
+ if (formValues.provider === "webhook") {
+ const {url, auth_mode, auth_value} = formValues
const previewHeaders: Record = buildSystemHeaders(
auth_mode === "authorization"
? {
@@ -233,7 +222,9 @@ export const buildPreviewRequest = (
headers: finalHeaders,
body: eventContext,
}
- } else if (provider === "github") {
+ } else if (formValues.provider === "github") {
+ const {github_sub_type, github_repo, github_pat, github_workflow, github_branch} =
+ formValues
const subType = github_sub_type || "repository_dispatch"
const repo = github_repo || "/"
let finalUrl = GITHUB_URL_TEMPLATES[subType].replace("{repo}", repo)
diff --git a/web/oss/src/components/Webhooks/utils/buildSubscription.ts b/web/oss/src/components/Webhooks/utils/buildSubscription.ts
index 73cd543e92..efd29fa8c8 100644
--- a/web/oss/src/components/Webhooks/utils/buildSubscription.ts
+++ b/web/oss/src/components/Webhooks/utils/buildSubscription.ts
@@ -14,20 +14,7 @@ export const buildSubscription = (
isEdit: boolean,
subscriptionId?: string,
): WebhookSubscriptionCreateRequest | WebhookSubscriptionEditRequest => {
- const {
- provider,
- name,
- event_types,
- url,
- headers,
- auth_mode,
- auth_value,
- github_sub_type,
- github_repo,
- github_pat,
- github_workflow,
- github_branch,
- } = formValues
+ const {provider, name, event_types} = formValues
// Common data
const baseSubscription = {
@@ -35,7 +22,8 @@ export const buildSubscription = (
...(isEdit && {id: subscriptionId}),
}
- if (provider === "webhook") {
+ if (formValues.provider === "webhook") {
+ const {url, headers, auth_mode, auth_value} = formValues
const subscription: WebhookSubscriptionCreateRequest["subscription"] = {
...baseSubscription,
data: {
@@ -52,7 +40,9 @@ export const buildSubscription = (
}
return {subscription}
- } else if (provider === "github") {
+ } else if (formValues.provider === "github") {
+ const {github_sub_type, github_repo, github_pat, github_workflow, github_branch} =
+ formValues
const subType = github_sub_type || "repository_dispatch"
const repo = github_repo || ""
let finalUrl = GITHUB_URL_TEMPLATES[subType].replace("{repo}", repo)
diff --git a/web/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsx b/web/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsx
index cf746b5e57..448b715892 100644
--- a/web/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsx
+++ b/web/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsx
@@ -6,7 +6,8 @@ import PlaygroundLoadingShell from "@/oss/components/PlaygroundRouter/Playground
interface OnboardingLoaderProps {
/** Mint failed — show a retry affordance instead of an endless skeleton. */
- error?: boolean
+ // Error | null covers next/dynamic's DynamicOptionsLoadingProps when used as a `loading` fallback.
+ error?: boolean | Error | null
onRetry?: () => void
}
diff --git a/web/oss/src/components/pages/app-management/modals/CreateAppStatusModal.tsx b/web/oss/src/components/pages/app-management/modals/CreateAppStatusModal.tsx
index 181e7eca3f..1e0ba664c2 100644
--- a/web/oss/src/components/pages/app-management/modals/CreateAppStatusModal.tsx
+++ b/web/oss/src/components/pages/app-management/modals/CreateAppStatusModal.tsx
@@ -8,7 +8,7 @@ import {usePlaygroundNavigation} from "@/oss/hooks/usePlaygroundNavigation"
import {getErrorMessage} from "@/oss/lib/helpers/errorHandler"
import {appCreationMessagesAtom, appCreationNavigationAtom} from "@/oss/state/appCreation/status"
import {resetAppCreationAtom} from "@/oss/state/appCreation/status"
-import type {AppCreationStatus} from "@/oss/state/appCreation/status"
+import type {AppCreationMessage, AppCreationStatus} from "@/oss/state/appCreation/status"
import CustomAppCreationLoader from "./CustomAppCreationLoader"
@@ -96,10 +96,13 @@ const CreateAppStatusModal: React.FC>
{
const lastStatus = Object.keys(draft).pop() ?? ""
if (!lastStatus) break
+ // index signature hides that the key may be absent
+ const lastMessage = draft[lastStatus] as AppCreationMessage | undefined
draft[lastStatus] = {
- ...(draft[lastStatus] ?? {
+ // fallback branch only runs when lastMessage is absent, so message is ""
+ ...(lastMessage ?? {
type: "error",
- message: draft[lastStatus]?.message ?? "",
+ message: "",
}),
type: "error",
errorMessage: `${getErrorMessage(details)}`,
diff --git a/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/hooks/types.d.ts b/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/hooks/types.d.ts
index b0fb28b7ef..8119635623 100644
--- a/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/hooks/types.d.ts
+++ b/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/hooks/types.d.ts
@@ -1,7 +1,7 @@
export interface useCustomWorkflowConfigProps {
folderId?: string | null
appId?: string | null
- afterConfigSave?: () => Promise
+ afterConfigSave?: () => unknown
setFetchingTemplate?: (value: SetStateAction) => void
setStatusModalOpen?: (value: SetStateAction) => void
}
diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalContent.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalContent.tsx
index bfb6bcdfe6..65bdae22a8 100644
--- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalContent.tsx
+++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalContent.tsx
@@ -1,7 +1,7 @@
import {type FC, memo, useCallback, useMemo} from "react"
import {workflowMolecule} from "@agenta/entities/workflow"
-import {createEvaluatorFromTemplate} from "@agenta/entities/workflow"
+import {createEvaluatorFromTemplate, type EvaluatorCatalogTemplate} from "@agenta/entities/workflow"
import {message} from "@agenta/ui/app-message"
import {CloseCircleOutlined} from "@ant-design/icons"
import {Input, Tabs, Tag, Typography} from "antd"
@@ -11,7 +11,6 @@ import dynamic from "next/dynamic"
import {openHumanEvaluatorDrawerAtom} from "@/oss/components/Evaluators/Drawers/HumanEvaluatorDrawer/store"
import useFocusInput from "@/oss/hooks/useFocusInput"
-import type {Evaluator} from "@/oss/lib/Types"
import {openEvaluatorDrawerAtom} from "@/oss/state/evaluator/evaluatorDrawerStore"
import TabLabel from "../assets/TabLabel"
@@ -119,7 +118,8 @@ const NewEvaluationModalContent: FC = ({
// Handler for opening the evaluator creation drawer with embedded playground
const handleSelectTemplate = useCallback(
- async (evaluator: Evaluator) => {
+ // EvaluatorTemplateDropdown (via SelectEvaluatorSection) emits catalog templates
+ async (evaluator: EvaluatorCatalogTemplate) => {
const templateKey = evaluator.key
if (!templateKey) {
message.error("Unable to open evaluator template")
@@ -377,7 +377,7 @@ const NewEvaluationModalContent: FC = ({
activeKey={activePanel || "appPanel"}
onChange={handlePanelChange as any}
items={items}
- tabPlacement="left"
+ tabPlacement="start" // antd 6 logical value; "start" === "left" (app is LTR-only)
className={clsx([
tabsContainerClass,
"[&_.ant-tabs-tab]:!p-2 [&_.ant-tabs-tab]:!mt-1",
diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx
index c756b78ee5..2bbbbd6c76 100644
--- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx
+++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx
@@ -217,14 +217,18 @@ const NewEvaluationModalInner = ({
}),
)
const evaluatorRowsByRevisionId = useMemo(() => {
- const map = new Map()
+ const map = new Map<
+ string,
+ {id: string; workflow_id: string; slug?: string; name?: string}
+ >()
for (const row of evaluatorStoreState.rows) {
if (!row.__isSkeleton && row.revisionId) {
map.set(row.revisionId, {
id: row.revisionId,
workflow_id: row.workflowId,
- slug: row.slug,
- name: row.name,
+ // Latent: store rows carry IDs only (display data lives in molecules), so slug/name are always undefined
+ slug: row.slug as string | undefined,
+ name: row.name as string | undefined,
})
}
}
diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx
index 83e8511d5c..69ba408518 100644
--- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx
+++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx
@@ -52,7 +52,7 @@ const SelectEvaluatorSection = ({
const setCategory = useSetAtom(evaluatorCategoryAtom)
const setStoreSearchTerm = useSetAtom(evaluatorSearchTermAtom)
const [searchTerm, setSearchTerm] = useState("")
- const prevSelectedAppIdRef = useRef()
+ const prevSelectedAppIdRef = useRef(undefined)
const category = preview ? "human" : "automatic"
diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/types.ts b/web/oss/src/components/pages/evaluations/NewEvaluation/types.ts
index c6cbf45b45..dab8ac8f3c 100644
--- a/web/oss/src/components/pages/evaluations/NewEvaluation/types.ts
+++ b/web/oss/src/components/pages/evaluations/NewEvaluation/types.ts
@@ -3,8 +3,7 @@ import type {Dispatch, HTMLProps, SetStateAction} from "react"
import type {EvaluatorCatalogTemplate, Workflow} from "@agenta/entities/workflow"
import {ModalProps} from "antd"
-import {Evaluator, SimpleEvaluator, testset} from "@/oss/lib/Types"
-import {EvaluatorDto} from "@/oss/services/evaluations/api/evaluatorTypes"
+import type {Testset} from "@/oss/state/entities/testset"
export interface NewEvaluationAppOption {
label: string
@@ -52,9 +51,10 @@ export interface NewEvaluationModalContentProps extends HTMLProps>
setEvaluationName: Dispatch>
isOpen?: boolean
- testsets: testset[]
- evaluators: EvaluatorCatalogTemplate[] | Evaluator[] | EvaluatorDto<"response">[]
- evaluatorConfigs: SimpleEvaluator[]
+ testsets: Testset[]
+ /** Catalog templates in auto mode; workflow-based evaluator lists in preview mode */
+ evaluators: EvaluatorCatalogTemplate[] | Workflow[]
+ evaluatorConfigs: Workflow[]
advanceSettings: EvaluationConcurrencySettings
setAdvanceSettings: Dispatch>
appOptions: NewEvaluationAppOption[]
@@ -76,7 +76,7 @@ export interface SelectVariantSectionProps extends HTMLProps {
}
export interface SelectTestsetSectionProps extends HTMLProps {
- testsets?: testset[]
+ testsets?: Testset[]
selectedTestsetId: string
selectedTestsetRevisionId?: string
setSelectedTestsetId: Dispatch>
diff --git a/web/oss/src/components/pages/evaluations/cellRenderers/cellRenderers.tsx b/web/oss/src/components/pages/evaluations/cellRenderers/cellRenderers.tsx
index 65f2d7c731..04a84761e8 100644
--- a/web/oss/src/components/pages/evaluations/cellRenderers/cellRenderers.tsx
+++ b/web/oss/src/components/pages/evaluations/cellRenderers/cellRenderers.tsx
@@ -174,7 +174,10 @@ export const ResultRenderer = memo(
export const runningStatuses = [EvaluationStatus.INITIALIZED, EvaluationStatus.STARTED]
export const statusMapper =
(token: GlobalToken) => (status: EvaluationStatus | "stopped" | "closed") => {
- const statusMap = {
+ // Partial: not every EvaluationStatus is mapped (e.g. FAILED) — unmapped fall to "Unknown"
+ const statusMap: Partial<
+ Record
+ > = {
[EvaluationStatus.PENDING]: {
label: "Pending",
color: token.colorTextSecondary,
diff --git a/web/oss/src/components/pages/evaluations/onlineEvaluation/assets/helpers.ts b/web/oss/src/components/pages/evaluations/onlineEvaluation/assets/helpers.ts
index c0d4059bce..6b296481b9 100644
--- a/web/oss/src/components/pages/evaluations/onlineEvaluation/assets/helpers.ts
+++ b/web/oss/src/components/pages/evaluations/onlineEvaluation/assets/helpers.ts
@@ -1,5 +1,5 @@
import {inferReferenceOptionKey} from "@/oss/components/pages/observability/assets/filters/referenceUtils"
-import type {Filter, FilterConditions} from "@/oss/lib/Types"
+import type {Filter, FilterConditions, FilterValue} from "@/oss/lib/Types"
import type {
QueryConditionPayload,
@@ -81,15 +81,17 @@ export const fromFilteringPayload = (payload?: QueryFilteringPayload | null): Fi
return collectConditions(payload).map((condition) => {
const rawOperator = (condition.operator ?? "is").trim()
const operator = (rawOperator || "is") as FilterConditions
+ // Wire payload declares value as unknown; trusted as FilterValue at this boundary
+ const value = condition.value as FilterValue
const key =
condition.field === "references"
- ? inferReferenceOptionKey(condition.value, condition.key)
+ ? inferReferenceOptionKey(value, condition.key)
: condition.key
return {
field: (condition.field ?? condition.key ?? "") as string,
key,
operator,
- value: condition.value,
+ value,
}
})
}
diff --git a/web/oss/src/components/pages/evaluations/onlineEvaluation/components/FiltersPreview.tsx b/web/oss/src/components/pages/evaluations/onlineEvaluation/components/FiltersPreview.tsx
index a88444ae2e..84c2a1434f 100644
--- a/web/oss/src/components/pages/evaluations/onlineEvaluation/components/FiltersPreview.tsx
+++ b/web/oss/src/components/pages/evaluations/onlineEvaluation/components/FiltersPreview.tsx
@@ -23,6 +23,8 @@ interface FiltersPreviewProps {
filters?: Filter[]
className?: string
compact?: boolean
+ // Accepted by callers but not consumed yet — typed as-is per WP-4e-2a.
+ compactMaxRows?: number
}
interface NormalizedFilter {
@@ -74,7 +76,7 @@ const buildNormalizedFilters = (
const fieldLabel = cfg?.label ?? filter.key ?? filter.field ?? "-"
const operator = filter.operator as FilterConditions
- let operatorLabel = operator || "is"
+ let operatorLabel: string = operator || "is"
if (cfg?.operatorOptions) {
const match = cfg.operatorOptions.find((opt) => opt.value === operator)
if (match?.label) {
diff --git a/web/oss/src/components/pages/evaluations/utils.ts b/web/oss/src/components/pages/evaluations/utils.ts
index e2b2b34254..914ae9d999 100644
--- a/web/oss/src/components/pages/evaluations/utils.ts
+++ b/web/oss/src/components/pages/evaluations/utils.ts
@@ -18,6 +18,7 @@ const parseInvocationMetadata = (
appId?: string
appName?: string
revisionId?: string
+ variantId?: string
variantName?: string
revisionLabel?: string | number
} | null => {
@@ -83,6 +84,13 @@ const parseInvocationMetadata = (
invocationStep.key,
)
+ const rawVariantId = pickString(
+ variantRef?.variantId,
+ variantRef?.variant_id,
+ applicationRevision?.variantId,
+ applicationRevision?.variant_id,
+ )
+
const rawRevisionId = pickString(
variantRef?.id,
variantRef?.revisionId,
@@ -108,6 +116,7 @@ const parseInvocationMetadata = (
appId: rawAppId,
appName: rawAppName,
revisionId: rawRevisionId,
+ variantId: rawVariantId,
variantName: rawVariantName,
revisionLabel: revisionLabel ?? undefined,
}
@@ -119,6 +128,7 @@ export const extractPrimaryInvocation = (
appId?: string
appName?: string
revisionId?: string
+ variantId?: string
variantName?: string
revisionLabel?: string | number
} | null => {
@@ -141,6 +151,11 @@ export const extractPrimaryInvocation = (
(variant as any)?.id ||
(typeof variant?.revisionId === "string" ? variant.revisionId : undefined) ||
(typeof variant?.revision_id === "string" ? variant.revision_id : undefined),
+ variantId:
+ (typeof variant?.variantId === "string" ? variant.variantId : undefined) ||
+ (typeof (variant as any)?.variant_id === "string"
+ ? (variant as any).variant_id
+ : undefined),
variantName: variant?.variantName || variant?.name || (variant as any)?.slug,
revisionLabel:
(variant as any)?.revisionLabel ||
@@ -153,6 +168,7 @@ export const extractPrimaryInvocation = (
appId: metadataFromSteps?.appId ?? variantDetails?.appId,
appName: metadataFromSteps?.appName ?? variantDetails?.appName,
revisionId: variantDetails?.revisionId ?? metadataFromSteps?.revisionId,
+ variantId: variantDetails?.variantId ?? metadataFromSteps?.variantId,
variantName: variantDetails?.variantName ?? metadataFromSteps?.variantName,
revisionLabel: variantDetails?.revisionLabel ?? metadataFromSteps?.revisionLabel,
}
diff --git a/web/oss/src/components/pages/observability/assets/getObservabilityColumns.tsx b/web/oss/src/components/pages/observability/assets/getObservabilityColumns.tsx
index 09ad7a45b2..e8cb6e65b3 100644
--- a/web/oss/src/components/pages/observability/assets/getObservabilityColumns.tsx
+++ b/web/oss/src/components/pages/observability/assets/getObservabilityColumns.tsx
@@ -1,9 +1,12 @@
+import type {Key} from "react"
+
import {LastInputMessageCell, SmartCellContent} from "@agenta/ui/cell-renderers"
import {CopyTooltip as TooltipWithCopyAction} from "@agenta/ui/copy-tooltip"
import {ColumnVisibilityMenuTrigger} from "@agenta/ui/table"
import {Tag} from "antd"
import {ColumnsType} from "antd/es/table"
+import type {ExtendedColumnType} from "@/oss/components/InfiniteVirtualTable"
import {sanitizeDataWithBlobUrls} from "@/oss/lib/helpers/utils"
import {TraceSpanNode} from "@/oss/services/tracing/types"
import {
@@ -26,6 +29,12 @@ interface ObservabilityColumnsProps {
evaluatorSlugs: string[]
}
+// Row alias: TraceSpanNode lacks the required key + index signature of InfiniteTableRowBase.
+export type TraceRow = TraceSpanNode & {key: Key; [key: string]: unknown}
+
+// antd column extended with props consumed by the InfiniteVirtualTable layer.
+type ObservabilityColumn = ExtendedColumnType
+
const collectDefaultHiddenColumnKeys = (columns: ColumnsType): string[] => {
const hiddenKeys = new Set()
@@ -47,7 +56,7 @@ const collectDefaultHiddenColumnKeys = (columns: ColumnsType): string[] =
}
export const getObservabilityColumns = ({evaluatorSlugs}: ObservabilityColumnsProps) => {
- const columns: ColumnsType = [
+ const columns: ObservabilityColumn[] = [
{
title: "ID",
dataIndex: ["span_id"],
@@ -182,7 +191,7 @@ export const getObservabilityColumns = ({evaluatorSlugs}: ObservabilityColumnsPr
}),
render: (_, record) => {
const duration = getLatency(record)
- return
+ return
},
},
{
@@ -195,7 +204,7 @@ export const getObservabilityColumns = ({evaluatorSlugs}: ObservabilityColumnsPr
}),
render: (_, record) => {
const cost = getCost(record)
- return
+ return
},
},
{
@@ -208,7 +217,7 @@ export const getObservabilityColumns = ({evaluatorSlugs}: ObservabilityColumnsPr
}),
render: (_, record) => {
const tokens = getTokens(record)
- return
+ return
},
},
{
diff --git a/web/oss/src/components/pages/observability/components/NodeNameCell.tsx b/web/oss/src/components/pages/observability/components/NodeNameCell.tsx
index fe42d998a0..5e36827eb1 100644
--- a/web/oss/src/components/pages/observability/components/NodeNameCell.tsx
+++ b/web/oss/src/components/pages/observability/components/NodeNameCell.tsx
@@ -7,7 +7,7 @@ import {SpanCategory} from "@/oss/services/tracing/types"
import {spanTypeStyles} from "../assets/constants"
interface Props {
- name: string
+ name?: string
type?: SpanCategory
}
diff --git a/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx b/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx
index 9dde4558f4..077e0906f0 100644
--- a/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx
+++ b/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx
@@ -17,6 +17,7 @@ import {SortResult} from "@/oss/components/Filters/Sort"
import type {FilterItem} from "@/oss/components/Filters/types"
import {fieldConfigByOptionKey} from "@/oss/components/pages/observability/assets/filters/fieldAdapter"
import AddActionsDropdown from "@/oss/components/SharedActions/AddActionsDropdown"
+import type {TestsetTraceData} from "@/oss/components/SharedDrawers/AddToTestsetDrawer/assets/types"
import {deleteTraceModalAtom} from "@/oss/components/SharedDrawers/TraceDrawer/components/DeleteTraceModal/store/atom"
import useLazyEffect from "@/oss/hooks/useLazyEffect"
import {useProjectPermissions} from "@/oss/hooks/useProjectPermissions"
@@ -358,11 +359,12 @@ const ObservabilityHeader = ({
const extractData = selectedRowKeys.map((key, idx) => {
const node = getNodeById(traces, key as string)
- return {data: getAgData(node) as KeyValuePair, key: node?.key, id: idx + 1}
+ return {data: getAgData(node ?? undefined) as KeyValuePair, key: node?.key, id: idx + 1}
})
if (extractData.length > 0) {
- setTestsetDrawerData(extractData)
+ // Latent: `key` can be undefined when a selected row is no longer in `traces`.
+ setTestsetDrawerData(extractData as TestsetTraceData[])
}
}, [traces, selectedRowKeys, setTestsetDrawerData])
diff --git a/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx b/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
index d6a94663ae..feeae3425c 100644
--- a/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
+++ b/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
@@ -16,6 +16,7 @@ import {hasReceivedTracesAtom} from "@/oss/state/newObservability/atoms/controls
import {
getDefaultHiddenObservabilityColumnKeys,
getObservabilityColumns,
+ type TraceRow,
} from "../../assets/getObservabilityColumns"
import {AUTO_REFRESH_INTERVAL} from "../../constants"
@@ -250,9 +251,9 @@ const ObservabilityTable = () => {
const showOnboarding = isNewUser && !hasReceivedTraces
// Build pagination object expected by InfiniteVirtualTableFeatureShell
- const pagination: TableFeaturePagination = useMemo(
+ const pagination: TableFeaturePagination = useMemo(
() => ({
- rows: traces,
+ rows: traces as TraceRow[],
loadNextPage: () => fetchMoreTraces(),
resetPages: resetTracePages,
paginationInfo: {
@@ -286,10 +287,10 @@ const ObservabilityTable = () => {
) : isEmptyState ? (
) : (
-
tableScope={tableScope}
columns={columns}
- rowKey={(record) => record.span_id || record.key}
+ rowKey={(record) => record.span_id || record.key || ""}
pagination={pagination}
resizableColumns
enableExport={false}
diff --git a/web/oss/src/components/pages/observability/components/SessionsTable/assets/getSessionColumns.tsx b/web/oss/src/components/pages/observability/components/SessionsTable/assets/getSessionColumns.tsx
index 6c363fca32..492f3ff674 100644
--- a/web/oss/src/components/pages/observability/components/SessionsTable/assets/getSessionColumns.tsx
+++ b/web/oss/src/components/pages/observability/components/SessionsTable/assets/getSessionColumns.tsx
@@ -1,7 +1,8 @@
import {Key} from "react"
import {ColumnVisibilityMenuTrigger} from "@agenta/ui/table"
-import {ColumnsType} from "antd/es/table"
+
+import type {ExtendedColumnType} from "@/oss/components/InfiniteVirtualTable"
import {
DurationCell,
@@ -21,9 +22,14 @@ export interface SessionRow {
key: Key
isSkeleton?: boolean
__isSkeleton?: boolean
+ // Index signature required by InfiniteTableRowBase.
+ [key: string]: unknown
}
-export const getSessionColumns = (): ColumnsType => [
+// antd column extended with props consumed by the InfiniteVirtualTable layer.
+type SessionColumn = ExtendedColumnType
+
+export const getSessionColumns = (): SessionColumn[] => [
{
title: "Session id",
dataIndex: "session_id",
diff --git a/web/oss/src/components/pages/observability/components/SessionsTable/index.tsx b/web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
index f6d0d7a18b..19128c6ae1 100644
--- a/web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
+++ b/web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
@@ -120,7 +120,8 @@ const SessionsTable: React.FC = () => {
const isEmptyState = sessionIds.length === 0 && !isLoading
return (
-
+ // Page store == default store (GlobalStateProvider), so this matches the prior fallback.
+
{
diff --git a/web/oss/src/components/pages/observability/dashboard/CustomAreaChart.tsx b/web/oss/src/components/pages/observability/dashboard/CustomAreaChart.tsx
index bcca55bda9..457f68ef70 100644
--- a/web/oss/src/components/pages/observability/dashboard/CustomAreaChart.tsx
+++ b/web/oss/src/components/pages/observability/dashboard/CustomAreaChart.tsx
@@ -99,7 +99,7 @@ const CustomAreaChart: React.FC = ({
}}
itemStyle={{color: token.colorText}}
labelStyle={{color: token.colorTextSecondary, marginBottom: 8}}
- formatter={(value: number) => [valueFormatter(value), ""]}
+ formatter={(value) => [valueFormatter(value as number), ""]}
/>
{categories.map((category, idx) => {
const colorKey = colors[idx % colors.length]
diff --git a/web/oss/src/components/pages/overview/variants/VariantPopover.tsx b/web/oss/src/components/pages/overview/variants/VariantPopover.tsx
index ee9a4fd535..27aea00635 100644
--- a/web/oss/src/components/pages/overview/variants/VariantPopover.tsx
+++ b/web/oss/src/components/pages/overview/variants/VariantPopover.tsx
@@ -1,11 +1,10 @@
import type {AppEnvironmentDeployment} from "@agenta/entities/environment"
import {useUserDisplayName} from "@agenta/entities/shared/user"
import type {Workflow} from "@agenta/entities/workflow"
-import {VariantNameCell} from "@agenta/entity-ui/variant"
+import {statusMap, VariantNameCell} from "@agenta/entity-ui/variant"
import {ArrowSquareOut} from "@phosphor-icons/react"
import {Badge, Button, Flex, Popover, Tag, Typography} from "antd"
-import {statusMap} from "@/oss/components/VariantDetailsWithStatus/components/EnvironmentStatus"
import {usePlaygroundNavigation} from "@/oss/hooks/usePlaygroundNavigation"
import {formatVariantIdWithHash} from "@/oss/lib/helpers/utils"
@@ -46,7 +45,7 @@ const VariantPopover = ({env, selectedDeployedVariant, ...props}: VariantPopover
icon={}
className="flex items-center justify-center"
onClick={() => {
- goToPlayground(env.deployedRevisionId)
+ goToPlayground(env.deployedRevisionId ?? undefined)
}}
/>
diff --git a/web/oss/src/components/pages/settings/Organization/index.tsx b/web/oss/src/components/pages/settings/Organization/index.tsx
index b467ab7ee9..660e2a6c0c 100644
--- a/web/oss/src/components/pages/settings/Organization/index.tsx
+++ b/web/oss/src/components/pages/settings/Organization/index.tsx
@@ -150,7 +150,7 @@ const Organization: FC = () => {
const updated = await updateOrganization(selectedOrg.id, payload, ignoreAxiosError)
if (updated) {
queryClient.setQueryData(["selectedOrg", selectedOrg.id], updated)
- queryClient.setQueriesData(["orgs"], (old: any) => {
+ queryClient.setQueriesData({queryKey: ["orgs"]}, (old: any) => {
if (!Array.isArray(old)) return old
return old.map((org) =>
org.id === updated.id ? {...org, ...updated} : org,
@@ -362,7 +362,6 @@ const Organization: FC = () => {
onError: (error: any) => {
message.error(error?.response?.data?.detail || "Failed to add SSO provider")
},
- useErrorBoundary: false,
throwOnError: false,
})
@@ -379,7 +378,6 @@ const Organization: FC = () => {
onError: (error: any) => {
message.error(error?.response?.data?.detail || "Failed to update SSO provider")
},
- useErrorBoundary: false,
throwOnError: false,
})
@@ -392,7 +390,6 @@ const Organization: FC = () => {
onError: (error: any) => {
message.error(error?.response?.data?.detail || "SSO provider connection test failed")
},
- useErrorBoundary: false,
throwOnError: false,
})
@@ -405,7 +402,6 @@ const Organization: FC = () => {
onError: (error: any) => {
message.error(error?.response?.data?.detail || "Failed to delete SSO provider")
},
- useErrorBoundary: false,
throwOnError: false,
})
diff --git a/web/oss/src/hooks/usePostAuthRedirect.ts b/web/oss/src/hooks/usePostAuthRedirect.ts
index 93141da62f..a52f489f29 100644
--- a/web/oss/src/hooks/usePostAuthRedirect.ts
+++ b/web/oss/src/hooks/usePostAuthRedirect.ts
@@ -1,6 +1,6 @@
import {useCallback, useMemo} from "react"
-import {getDefaultStore, useSetAtom} from "jotai"
+import {getDefaultStore, useSetAtom, type Atom} from "jotai"
import {useRouter} from "next/router"
import Session, {signOut} from "supertokens-auth-react/recipe/session"
import {useLocalStorage} from "usehooks-ts"
diff --git a/web/oss/src/lib/Types.ts b/web/oss/src/lib/Types.ts
index 2489102eaa..ecb50ffe8c 100644
--- a/web/oss/src/lib/Types.ts
+++ b/web/oss/src/lib/Types.ts
@@ -68,6 +68,18 @@ export interface Testset {
columns?: string[]
}
+export interface PreviewTestCase {
+ created_at: string
+ created_by_id: string
+
+ id: string
+ set_id: string
+ testset_id: string
+ data: Record
+ /** Legacy testcases carry inputs instead of data */
+ inputs?: Record
+}
+
export interface PreviewTestset {
id: string
name: string
@@ -322,6 +334,23 @@ export enum EvaluationStatusType {
ERROR = "error",
}
+export interface Parameter {
+ name: string
+ type: string
+ input: boolean
+ required: boolean
+ default?: any
+ enum?: string[]
+ minimum?: number
+ maximum?: number
+ choices?: Record
+}
+
+export interface CorrectAnswer {
+ key: string
+ value: string
+}
+
export interface _Evaluation {
id: string
appId: string
@@ -353,6 +382,19 @@ export interface _Evaluation {
variant_revision_ids: string[]
}
+export interface _EvaluationScenario {
+ id: string
+ evaluation_id: string
+ evaluation: _Evaluation
+ evaluators_configs: EvaluatorConfig[]
+ inputs: (TypedValue & {name: string})[]
+ outputs: {result: TypedValue; cost?: number; latency?: number}[]
+ correct_answers?: CorrectAnswer[]
+ is_pinned?: boolean
+ note?: string
+ results: {evaluator_config: string; result: TypedValue & {error: null | EvaluationError}}[]
+}
+
type ValueType = number | string | boolean | GenericObject | null
type ValueTypeOptions =
| "text"
diff --git a/web/oss/src/lib/api/assets/fetchClient.ts b/web/oss/src/lib/api/assets/fetchClient.ts
index 91899db30b..f5571e46d4 100644
--- a/web/oss/src/lib/api/assets/fetchClient.ts
+++ b/web/oss/src/lib/api/assets/fetchClient.ts
@@ -40,7 +40,7 @@ export function ensureProjectId(existing?: string): string | undefined {
try {
const store = getDefaultStore()
const pid = store.get(projectIdAtom)
- return pid
+ return pid ?? undefined
} catch {
return undefined
}
diff --git a/web/oss/src/lib/evaluations/legacy.ts b/web/oss/src/lib/evaluations/legacy.ts
index 8c44b121fd..6a546fa939 100644
--- a/web/oss/src/lib/evaluations/legacy.ts
+++ b/web/oss/src/lib/evaluations/legacy.ts
@@ -113,7 +113,9 @@ export function getFilterParams(type: CellDataType) {
}
}
-export const calcEvalDuration = (evaluation: _Evaluation) => {
+export const calcEvalDuration = (
+ evaluation: Pick<_Evaluation, "status" | "created_at" | "updated_at">,
+) => {
return dayjs(
runningStatuses.includes(evaluation.status.value) ? Date.now() : evaluation.updated_at,
).diff(dayjs(evaluation.created_at), "milliseconds")
diff --git a/web/oss/src/lib/helpers/analytics/AgPosthogProvider.tsx b/web/oss/src/lib/helpers/analytics/AgPosthogProvider.tsx
index c6f8a2c5fd..76bf18df82 100644
--- a/web/oss/src/lib/helpers/analytics/AgPosthogProvider.tsx
+++ b/web/oss/src/lib/helpers/analytics/AgPosthogProvider.tsx
@@ -2,6 +2,7 @@ import {useCallback, useEffect, useRef, useState} from "react"
import {useAtom} from "jotai"
import {useRouter} from "next/router"
+import type {PostHog} from "posthog-js"
import {getEnv} from "../dynamicEnv"
import {generateOrRetrieveDistinctId, isDemo} from "../utils"
@@ -40,7 +41,8 @@ const CustomPosthogProvider: CustomPosthogProviderType = ({children}) => {
ui_host: "https://us.posthog.com",
// Enable debug mode in development
loaded: (posthog) => {
- setPosthogClient(posthog)
+ // the loaded callback narrows to PostHogInterface; the instance is the full client
+ setPosthogClient(posthog as PostHog)
failedAttemptsRef.current = 0
if (process.env.NODE_ENV === "development") posthog.debug()
if (!initCapturedRef.current) {
diff --git a/web/oss/src/lib/helpers/dateTimeHelper/index.ts b/web/oss/src/lib/helpers/dateTimeHelper/index.ts
index 3a7dc454c0..693932bd33 100644
--- a/web/oss/src/lib/helpers/dateTimeHelper/index.ts
+++ b/web/oss/src/lib/helpers/dateTimeHelper/index.ts
@@ -21,7 +21,13 @@ export const formatDate24 = (date: dayjs.ConfigType, includeSeconds = false): st
return dayjs(date).format("DD MMM YY, HH:mm" + (includeSeconds ? ":ss" : ""))
}
-export const parseDate = ({date, inputFormat = "YYYY-MM-DD H:mm:sssAZ"}) => {
+export const parseDate = ({
+ date,
+ inputFormat = "YYYY-MM-DD H:mm:sssAZ",
+}: {
+ date: dayjs.ConfigType
+ inputFormat?: string
+}) => {
return dayjs(date, inputFormat)
}
diff --git a/web/oss/src/lib/hooks/useEvaluationRunMetrics/index.ts b/web/oss/src/lib/hooks/useEvaluationRunMetrics/index.ts
index 3f5f158ef0..77846eb0cf 100644
--- a/web/oss/src/lib/hooks/useEvaluationRunMetrics/index.ts
+++ b/web/oss/src/lib/hooks/useEvaluationRunMetrics/index.ts
@@ -39,7 +39,9 @@ const useEvaluationRunMetrics = (
const sorted = [...runIds].sort()
sorted.forEach((id) => queryParams.append("run_ids", id))
} else {
- queryParams.append("run_ids", runIds)
+ // Reached with a plain string, or an empty string[] (which URLSearchParams
+ // coerces to "" and the swrKey filter drops) — typed as-is per WP-4e-2a.
+ queryParams.append("run_ids", runIds as string)
}
}
if (options?.limit !== undefined) {
@@ -75,10 +77,11 @@ const useEvaluationRunMetrics = (
next?: string
}>(swrKey, fetcher)
- // Convert raw MetricResponse[] to camelCase Metric[]
+ // Latent bug typed as-is per WP-4e-2a: the "conversion" is an identity map, so the
+ // values keep their snake_case keys at runtime despite the camelCase Metric type.
const rawMetrics = swrData.data?.metrics
const camelMetrics: Metric[] | undefined = rawMetrics
- ? rawMetrics.map((item) => item)
+ ? (rawMetrics.map((item) => item) as unknown as Metric[])
: undefined
const totalCount = swrData.data?.count
diff --git a/web/oss/src/lib/hooks/useEvaluationRunMetrics/types.ts b/web/oss/src/lib/hooks/useEvaluationRunMetrics/types.ts
index 20de372a60..3dce4557c4 100644
--- a/web/oss/src/lib/hooks/useEvaluationRunMetrics/types.ts
+++ b/web/oss/src/lib/hooks/useEvaluationRunMetrics/types.ts
@@ -1,4 +1,10 @@
import {EvaluationStatus, SnakeToCamelCaseKeys} from "@/oss/lib/Types"
+import type {
+ computeRunMetrics,
+ createScenarioMetrics,
+ updateMetric,
+ updateMetrics,
+} from "@/oss/services/runMetrics/api"
// Raw API response type for one metric (snake_case)
export interface MetricResponse {
@@ -40,36 +46,9 @@ export interface UseEvaluationRunMetricsResult {
any
>
mutate: () => Promise
- createScenarioMetrics: (
- apiUrl: string,
- jwt: string,
- runId: string,
- entries: {
- scenarioId: string
- data: Record
- }[],
- ) => Promise
- updateMetric: (
- apiUrl: string,
- jwt: string,
- metricId: string,
- changes: {
- data?: Record
- status?: string
- tags?: Record
- meta?: Record
- },
- ) => Promise
- updateMetrics: (
- apiUrl: string,
- jwt: string,
- metrics: {
- id: string
- data?: Record
- status?: string
- tags?: Record
- meta?: Record
- }[],
- ) => Promise
- computeRunMetrics: (metrics: {data: Record}[]) => Record
+ // Mirror the actual service signatures (the hook re-exports them verbatim).
+ createScenarioMetrics: typeof createScenarioMetrics
+ updateMetric: typeof updateMetric
+ updateMetrics: typeof updateMetrics
+ computeRunMetrics: typeof computeRunMetrics
}
diff --git a/web/oss/src/lib/hooks/usePreviewEvaluations/index.ts b/web/oss/src/lib/hooks/usePreviewEvaluations/index.ts
index 19f7ec1bd3..11c2c03400 100644
--- a/web/oss/src/lib/hooks/usePreviewEvaluations/index.ts
+++ b/web/oss/src/lib/hooks/usePreviewEvaluations/index.ts
@@ -376,7 +376,7 @@ const usePreviewEvaluations = ({
data: tc.data ?? {},
}))
- const hydratedTestset: Testset = {
+ const hydratedTestset: NonNullable = {
...(rawTestset as Testset),
id: revision.testset_id,
// Prefer explicit name from caller, then revision name, then fallback
@@ -417,7 +417,14 @@ const usePreviewEvaluations = ({
throw new Error("Testset is required to create scenarios")
}
// 4. Creates the scenarios
- const scenarioIds = await createScenarios(runId, paramInputs.testset)
+ // Latent: a testset without hydrated `data` would throw inside createScenarios
+ // at runtime — typed as-is per WP-4e-2a.
+ const scenarioIds = await createScenarios(
+ runId,
+ paramInputs.testset as Testset & {
+ data: {testcaseIds?: string[]; testcases?: {id: string}[]}
+ },
+ )
// Fire off input, invocation, and annotation steps together in one request (non-blocking)
try {
@@ -440,16 +447,29 @@ const usePreviewEvaluations = ({
)
: "invocation"
- const scenarioStepsData = scenarioIds.map((scenarioId, index) => {
- const hashId = uuidv4()
- return {
- testcaseId:
- paramInputs.testset?.data?.testcaseIds?.[index] ??
- paramInputs.testset?.data?.testcases?.[index]?.id,
+ // repeatId/retryIdInput are never set (see the commented-out uuids above),
+ // so they are undefined at the destructure below — typed as-is per WP-4e-2a.
+ const scenarioStepsData = scenarioIds.map(
+ (
scenarioId,
- hashId,
- }
- })
+ index,
+ ): {
+ testcaseId?: string
+ scenarioId: string
+ hashId: string
+ repeatId?: string
+ retryIdInput?: string
+ } => {
+ const hashId = uuidv4()
+ return {
+ testcaseId:
+ paramInputs.testset?.data?.testcaseIds?.[index] ??
+ paramInputs.testset?.data?.testcases?.[index]?.id,
+ scenarioId,
+ hashId,
+ }
+ },
+ )
// 6. Build a single steps array combining input, invocation, and evaluator steps
const allSteps = scenarioStepsData.flatMap(
diff --git a/web/oss/src/lib/hooks/usePreviewEvaluations/types.ts b/web/oss/src/lib/hooks/usePreviewEvaluations/types.ts
index f2877f3865..2a56892fc3 100644
--- a/web/oss/src/lib/hooks/usePreviewEvaluations/types.ts
+++ b/web/oss/src/lib/hooks/usePreviewEvaluations/types.ts
@@ -46,6 +46,8 @@ export interface EvaluationRun {
description: string
/** ISO timestamp of when the run was created */
created_at: string
+ /** ISO timestamp of when the run was last updated (present on backend run payloads) */
+ updated_at?: string
/** ID of the user who created the run */
created_by_id: string
/** Optional metadata object (arbitrary key-value pairs) */
diff --git a/web/oss/src/lib/onboarding/widget/store.ts b/web/oss/src/lib/onboarding/widget/store.ts
index c4bc7243ab..21528c0683 100644
--- a/web/oss/src/lib/onboarding/widget/store.ts
+++ b/web/oss/src/lib/onboarding/widget/store.ts
@@ -57,7 +57,13 @@ export const onboardingWidgetUIStateAtom = atom(
if (!userId) return {isOpen: false, isMinimized: false}
return get(widgetUIAtomFamily(userId))
},
- (get, set, next: OnboardingWidgetUIState) => {
+ (
+ get,
+ set,
+ next:
+ | OnboardingWidgetUIState
+ | ((prev: OnboardingWidgetUIState) => OnboardingWidgetUIState),
+ ) => {
const userId = get(onboardingStorageUserIdAtom)
if (!userId) return
set(widgetUIAtomFamily(userId), next)
diff --git a/web/oss/src/lib/traces/traceUtils.ts b/web/oss/src/lib/traces/traceUtils.ts
index c4f94544e2..33a2f3ee2f 100644
--- a/web/oss/src/lib/traces/traceUtils.ts
+++ b/web/oss/src/lib/traces/traceUtils.ts
@@ -136,7 +136,7 @@ export function readInvocationResponse({
}
const resolvedCandidates = Array.from(
- new Set(candidatePaths.filter((p): p is string => typeof p === "string" && p.length)),
+ new Set(candidatePaths.filter((p): p is string => typeof p === "string" && p.length > 0)),
)
const resolvedPath = resolvedCandidates[0]
// --- END PATH RESOLUTION LOGIC ---
diff --git a/web/oss/src/pages/auth/[[...path]].tsx b/web/oss/src/pages/auth/[[...path]].tsx
index bc45f416fb..d8f17646fe 100644
--- a/web/oss/src/pages/auth/[[...path]].tsx
+++ b/web/oss/src/pages/auth/[[...path]].tsx
@@ -328,7 +328,8 @@ const Auth = () => {
}
} catch (err) {
const isCanceled =
- axios.isCancel?.(err) ||
+ // typed as-is: `isCancel` is a module static absent on this axios instance, so this is always undefined
+ (axios as {isCancel?: (value: unknown) => boolean}).isCancel?.(err) ||
(err as {code?: string}).code === "ERR_CANCELED" ||
(err instanceof Error &&
(err.name === "AbortError" ||
diff --git a/web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/overview/index.tsx b/web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/overview/index.tsx
index bd722d19e5..3e272f4c31 100644
--- a/web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/overview/index.tsx
+++ b/web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/overview/index.tsx
@@ -86,7 +86,9 @@ const AppDetailsSection = memo(() => {
openEditAppModal({
id: workflowId,
name: workflowName,
- onRenamed: () => mutateApps(),
+ onRenamed: async () => {
+ await mutateApps?.()
+ },
}),
},
]),
diff --git a/web/oss/src/pages/workspaces/accept.tsx b/web/oss/src/pages/workspaces/accept.tsx
index 1997c7ec28..aa0dc3593b 100644
--- a/web/oss/src/pages/workspaces/accept.tsx
+++ b/web/oss/src/pages/workspaces/accept.tsx
@@ -94,8 +94,9 @@ const Accept: FC = () => {
{
token,
organizationId,
- workspaceId,
- projectId,
+ // typed as-is: invite params can be absent; the request URL always interpolated them
+ workspaceId: workspaceId as string,
+ projectId: projectId as string,
email,
},
true,
@@ -146,7 +147,7 @@ const Accept: FC = () => {
const nextPath = buildPostLoginPath({
workspaceId: targetWorkspace,
- projectId,
+ projectId: projectId ?? null,
})
await router.replace(nextPath)
} else {
diff --git a/web/oss/src/services/evaluationRuns/api/index.ts b/web/oss/src/services/evaluationRuns/api/index.ts
index 53ea851a11..64eedd1afd 100644
--- a/web/oss/src/services/evaluationRuns/api/index.ts
+++ b/web/oss/src/services/evaluationRuns/api/index.ts
@@ -206,7 +206,9 @@ const buildMappings = (
// Generate input mappings aligned with Playground (schema + initial prompt vars for custom; prompt tokens for non-custom)
{
const store = getDefaultStore()
- const appContext = store.get(currentAppContextAtom)
+ const appContextValue = store.get(currentAppContextAtom)
+ // the eager atom can still hold a pending Promise; a Promise has no appType, so isCustom stays false
+ const appContext = appContextValue instanceof Promise ? null : appContextValue
const isCustom = appContext?.appType === "custom"
const spec = store.get(appOpenApiSchemaAtomFamily(revision.id))
const routePath = store.get(appRoutePathAtomFamily(revision.id)) || ""
@@ -298,7 +300,10 @@ const buildMappings = (
// Evaluator output mappings generated dynamically per evaluator
if (evaluators && evaluators.length > 0) {
evaluators?.forEach((evaluator) => {
- const metrics = getMetricsFromEvaluator(evaluator)
+ // typed as-is: a Workflow is passed where an EvaluatorDto is expected; only `.data` is read
+ const metrics = getMetricsFromEvaluator(
+ evaluator as unknown as Parameters[0],
+ )
Object.keys(metrics).forEach((key) => {
mappings.push({
column: {kind: "evaluator", name: `${evaluator.slug}.${key}`},
diff --git a/web/oss/src/services/evaluationRuns/api/types.ts b/web/oss/src/services/evaluationRuns/api/types.ts
index 88b9c7f65e..efe94de44e 100644
--- a/web/oss/src/services/evaluationRuns/api/types.ts
+++ b/web/oss/src/services/evaluationRuns/api/types.ts
@@ -6,6 +6,12 @@ import type {Testset as BaseTestset} from "@/oss/lib/Types"
export interface Testset extends BaseTestset {
variantId?: string
revisionId?: string
+ slug?: string
+ // Populated by revision hydration in usePreviewEvaluations.createNewRun.
+ data?: {
+ testcaseIds?: string[]
+ testcases?: {id: string; data?: Record}[]
+ }
}
export interface CreateEvaluationRunInput {
diff --git a/web/oss/src/services/evaluations/api/index.ts b/web/oss/src/services/evaluations/api/index.ts
index cc09fe113d..4211d84c7d 100644
--- a/web/oss/src/services/evaluations/api/index.ts
+++ b/web/oss/src/services/evaluations/api/index.ts
@@ -177,7 +177,7 @@ export const createEvaluation = async (appId: string, evaluation: CreateEvaluati
const applicationRevisionIds = revisionIds?.length ? revisionIds : [undefined]
const evaluatorSteps = evaluation.evaluator_revision_ids.reduce(
- (acc, id) => ({...acc, [id]: "auto"}),
+ (acc, id) => ({...acc, [id]: "auto" as const}),
{} as Record,
)
const testsetSteps = evaluation.testset_revision_id
diff --git a/web/oss/src/services/observability/types/index.ts b/web/oss/src/services/observability/types/index.ts
index 367c4fe964..61447eae0e 100644
--- a/web/oss/src/services/observability/types/index.ts
+++ b/web/oss/src/services/observability/types/index.ts
@@ -40,6 +40,10 @@ interface TreeContextDTO {
type?: NodeTreeType | null
}
export interface AgentaNodeDTO {
+ // Backend SpanDTO (api/oss/src/core/otel/dtos.py) sends these on every node;
+ // optional here because FE-built partial nodes may omit them.
+ trace_id?: string
+ span_id?: string
lifecycle?: NodeLifecycleDTO | null
time: NodeTimeDTO
status: NodeStatusDTO
diff --git a/web/oss/src/services/organization/api/index.ts b/web/oss/src/services/organization/api/index.ts
index 764135aab2..6db036a4f3 100644
--- a/web/oss/src/services/organization/api/index.ts
+++ b/web/oss/src/services/organization/api/index.ts
@@ -176,7 +176,7 @@ export const fetchOrganizationDomains = async (): Promise
*/
export const createOrganizationDomain = async (payload: {
domain: string
- name: string
+ name?: string
description?: string
}): Promise => {
const response = await axios.post(`${getAgentaApiUrl()}/organizations/domains/`, payload, {
@@ -196,7 +196,7 @@ export const verifyOrganizationDomain = async (domainId: string): Promise
// SSO/OIDC Provider API
// ============================================================================
+export interface OrganizationProviderSettings {
+ issuer_url?: string
+ client_id?: string
+ client_secret?: string
+ authorization_endpoint?: string
+ token_endpoint?: string
+ userinfo_endpoint?: string
+ scopes?: string[]
+}
+
export interface OrganizationProvider {
id: string
slug: string
organization_id: string
- provider_type: "oidc"
- name: string
- client_id: string
- client_secret: string
- issuer_url: string
- authorization_endpoint?: string
- token_endpoint?: string
- userinfo_endpoint?: string
- scopes: string[]
+ name?: string | null
+ description?: string | null
+ // Backend serves free-form dicts (OrganizationProviderResponse.flags/settings)
flags: {
is_valid?: boolean
is_active?: boolean
+ is_enabled?: boolean
}
+ settings: OrganizationProviderSettings
created_at: string
updated_at: string | null
}
@@ -266,13 +272,10 @@ export const fetchOrganizationProviders = async (): Promise
+ settings: OrganizationProviderSettings
}): Promise => {
const response = await axios.post(`${getAgentaApiUrl()}/organizations/providers/`, payload, {
_ignoreError: true,
@@ -287,15 +290,10 @@ export const updateOrganizationProvider = async (
providerId: string,
payload: {
slug?: string
- config?: {
- issuer_url?: string
- client_id?: string
- client_secret?: string
- scopes?: string[]
- }
- flags?: {
- is_enabled?: boolean
- }
+ name?: string
+ description?: string
+ settings?: OrganizationProviderSettings
+ flags?: Record
},
): Promise => {
const response = await axios.patch(
diff --git a/web/oss/src/services/runMetrics/api/index.ts b/web/oss/src/services/runMetrics/api/index.ts
index f175601fdd..0299d92b82 100644
--- a/web/oss/src/services/runMetrics/api/index.ts
+++ b/web/oss/src/services/runMetrics/api/index.ts
@@ -689,7 +689,8 @@ export const computeMetricDistribution = (
computed = agg[tmpKey]
}
if (!computed?.distribution || !computed.distribution.length) {
- return computed
+ // typed as-is: this path has always returned the raw stats (no distribution/binSize)
+ return computed as MetricDistribution | undefined
}
let binSize = computed.binSize
if (binSize === undefined) {
diff --git a/web/oss/src/services/tracing/types/index.ts b/web/oss/src/services/tracing/types/index.ts
index 816c273c75..f94a14b5e6 100644
--- a/web/oss/src/services/tracing/types/index.ts
+++ b/web/oss/src/services/tracing/types/index.ts
@@ -104,6 +104,8 @@ export interface TraceSpanNode extends TraceSpan {
span_id: string
}
children?: TraceSpan[]
+ /** Attached by the trace/session drawer stores when annotation data is loaded */
+ annotations?: import("@/oss/lib/hooks/useAnnotations/types").AnnotationDto[]
}
// --- RESPONSE WRAPPER --------------------------------------------------------
diff --git a/web/oss/src/services/workspace/index.ts b/web/oss/src/services/workspace/index.ts
index 3485998528..e910aa1fab 100644
--- a/web/oss/src/services/workspace/index.ts
+++ b/web/oss/src/services/workspace/index.ts
@@ -26,12 +26,13 @@ export const fetchWorkspaceMembers = async (workspaceId: string): Promise & {status?: number; statusText?: string}
console.error("❌ Workspace members fetcher failed:", {
- message: error?.message,
- status: error?.status,
- statusText: error?.statusText,
+ message: err?.message,
+ status: err?.status,
+ statusText: err?.statusText,
url: url.toString(),
- stack: error?.stack?.split("\n").slice(0, 3).join("\n"),
+ stack: err?.stack?.split("\n").slice(0, 3).join("\n"),
})
return []
}
diff --git a/web/oss/src/state/app/selectors/app.ts b/web/oss/src/state/app/selectors/app.ts
index 88e3d61d4d..8783552335 100644
--- a/web/oss/src/state/app/selectors/app.ts
+++ b/web/oss/src/state/app/selectors/app.ts
@@ -14,7 +14,8 @@ export const appsAtom = eagerAtom((get) => {
return get(appsQueryAtom).data ?? EmptyApps
})
-export const selectedAppIdAtom = eagerAtom((get) => {
+// Plain atom (not eagerAtom): both deps are synchronous, so the value is never a Promise.
+export const selectedAppIdAtom = atom((get) => {
return get(routerAppIdAtom) || get(recentAppIdAtom) || null
})
diff --git a/web/oss/src/state/appCreation/status.ts b/web/oss/src/state/appCreation/status.ts
index 411f511013..27a41b68a5 100644
--- a/web/oss/src/state/appCreation/status.ts
+++ b/web/oss/src/state/appCreation/status.ts
@@ -52,7 +52,8 @@ const messagesAtom = atomWithImmer>({})
export type AppCreationMessagesUpdate =
| Record
- | ((prev: Record) => Record)
+ // updater may mutate the immer draft in place (void) or return the next record
+ | ((prev: Record) => Record | void)
export const appCreationMessagesAtom = atom(
(get) => get(messagesAtom),
diff --git a/web/oss/src/state/appState/hooks.ts b/web/oss/src/state/appState/hooks.ts
index 10c5f6f0fb..dfa4a005d1 100644
--- a/web/oss/src/state/appState/hooks.ts
+++ b/web/oss/src/state/appState/hooks.ts
@@ -24,7 +24,7 @@ const arraysEqual = (a?: string[] | null, b?: string[] | null) => {
return true
}
-const toPatchValue = (value: QueryValue): string | string[] | undefined => {
+const toPatchValue = (value: QueryValue | null): string | string[] | undefined => {
if (value === null || value === undefined) return undefined
if (Array.isArray(value)) return value
return String(value)
@@ -110,7 +110,8 @@ export const useQueryParamState = (
): readonly [
QueryValue,
(
- value: QueryValue | ((prev: QueryValue) => QueryValue),
+ // `null` clears the param (toPatchValue maps it to undefined)
+ value: QueryValue | null | ((prev: QueryValue) => QueryValue | null),
options?: QueryNavigationOptions,
) => void,
] => {
@@ -122,7 +123,7 @@ export const useQueryParamState = (
const setValue = useCallback(
(
- value: QueryValue | ((prev: QueryValue) => QueryValue),
+ value: QueryValue | null | ((prev: QueryValue) => QueryValue | null),
options?: QueryNavigationOptions,
) => {
const resolved = typeof value === "function" ? (value as any)(currentValue) : value
diff --git a/web/oss/src/state/entities/shared/createPaginatedEntityStore.ts b/web/oss/src/state/entities/shared/createPaginatedEntityStore.ts
index 48716e0c72..e8aa75f44b 100644
--- a/web/oss/src/state/entities/shared/createPaginatedEntityStore.ts
+++ b/web/oss/src/state/entities/shared/createPaginatedEntityStore.ts
@@ -253,7 +253,7 @@ export interface PaginatedEntityStore<
* refresh() // increments and triggers refetch
* ```
*/
- refreshAtom: WritableAtom
+ refreshAtom: WritableAtom number)], void>
/**
* Meta atom providing the query parameters.
@@ -348,7 +348,7 @@ export interface PaginatedEntityStore<
* refresh()
* ```
*/
- refresh: WritableAtom
+ refresh: WritableAtom number)], void>
}
}
@@ -417,11 +417,11 @@ export function createPaginatedEntityStore<
excludeRowIdsAtom,
})
- // Create writable refresh atom
+ // Create writable refresh atom (setState-style value/updater optional; bare set() bumps the counter)
const refreshAtom = atom(
(get) => get(internalRefreshAtom),
- (_get, set) => {
- set(internalRefreshAtom, (prev) => prev + 1)
+ (_get, set, next?: number | ((prev: number) => number)) => {
+ set(internalRefreshAtom, next ?? ((prev: number) => prev + 1))
},
)
diff --git a/web/oss/src/state/newObservability/atoms/controls.ts b/web/oss/src/state/newObservability/atoms/controls.ts
index a300751a51..f622829d57 100644
--- a/web/oss/src/state/newObservability/atoms/controls.ts
+++ b/web/oss/src/state/newObservability/atoms/controls.ts
@@ -218,7 +218,8 @@ export const searchQueryAtom = atom(
export const traceTabsAtom = atom(
(get) => get(traceTabsAtomFamily(get(observabilityTabAtom))),
- (get, set, value: TraceTabTypes) => set(traceTabsAtomFamily(get(observabilityTabAtom)), value),
+ (get, set, value: TraceTabTypes | ((prev: TraceTabTypes) => TraceTabTypes)) =>
+ set(traceTabsAtomFamily(get(observabilityTabAtom)), value),
)
export const limitAtom = atom(
@@ -382,7 +383,8 @@ export const filtersAtomFamily = atomFamily((tab: ObservabilityTabInfo) =>
// Proxy filters atom
export const filtersAtom = atom(
(get) => get(filtersAtomFamily(get(observabilityTabAtom))),
- (get, set, update: Filter[]) => set(filtersAtomFamily(get(observabilityTabAtom)), update),
+ (get, set, update: Filter[] | ((prev: Filter[]) => Filter[])) =>
+ set(filtersAtomFamily(get(observabilityTabAtom)), update),
)
// Table/UI controls -----------------------------------------------------------
diff --git a/web/oss/src/state/newObservability/atoms/queries.ts b/web/oss/src/state/newObservability/atoms/queries.ts
index 7dbeed7256..45d2bb3b9a 100644
--- a/web/oss/src/state/newObservability/atoms/queries.ts
+++ b/web/oss/src/state/newObservability/atoms/queries.ts
@@ -246,7 +246,7 @@ export const traceAnnotationInfoAtomFamily = atomFamily((key: string) =>
)
// Formatting helpers ----------------------------------------------------------
-export const formattedTimestampAtomFamily = atomFamily((ts?: string) =>
+export const formattedTimestampAtomFamily = atomFamily((ts?: string | number) =>
atom(() => formatDay({date: ts, outputFormat: "HH:mm:ss DD MMM YYYY"})),
)
diff --git a/web/oss/src/state/newObservability/atoms/queryHelpers.ts b/web/oss/src/state/newObservability/atoms/queryHelpers.ts
index 2946cd5dae..7787e4164a 100644
--- a/web/oss/src/state/newObservability/atoms/queryHelpers.ts
+++ b/web/oss/src/state/newObservability/atoms/queryHelpers.ts
@@ -364,10 +364,16 @@ export const executeTraceQuery = async ({
// transform to tree
const transformed: TraceSpanNode[] = []
+ // entities-package TraceSpanNode is the same backend span shape as the OSS type;
+ // align the annotation at the boundary, no data is converted.
if (isTracesResponse(data)) {
- transformed.push(...transformTracingResponse(transformTracesResponseToTree(data)))
+ transformed.push(
+ ...(transformTracingResponse(
+ transformTracesResponseToTree(data),
+ ) as unknown as TraceSpanNode[]),
+ )
} else if (isSpansResponse(data)) {
- transformed.push(...transformTracingResponse(data.spans))
+ transformed.push(...(transformTracingResponse(data.spans) as unknown as TraceSpanNode[]))
}
// cursor
diff --git a/web/oss/src/state/newObservability/helpers/index.ts b/web/oss/src/state/newObservability/helpers/index.ts
index e430435b88..db37bbdef8 100644
--- a/web/oss/src/state/newObservability/helpers/index.ts
+++ b/web/oss/src/state/newObservability/helpers/index.ts
@@ -1,3 +1,6 @@
+import type {FilterItem} from "@/oss/components/Filters/types"
+import type {Filter} from "@/oss/lib/Types"
+
export const parseNumericString = (raw: string) => {
const trimmed = raw.trim()
if (!trimmed) return raw
diff --git a/web/oss/src/state/newObservability/selectors/tracing.ts b/web/oss/src/state/newObservability/selectors/tracing.ts
index 0596e498e0..793df96327 100644
--- a/web/oss/src/state/newObservability/selectors/tracing.ts
+++ b/web/oss/src/state/newObservability/selectors/tracing.ts
@@ -6,8 +6,37 @@ import {atomFamily} from "jotai/utils"
import {getStringOrJson} from "@/oss/lib/helpers/utils"
import {TraceSpanNode} from "@/oss/services/tracing/types/index"
+// Minimal runtime shape of `attributes.ag` (backend extra="allow" data).
+interface AgMetricBuckets {
+ total?: number
+ prompt?: number
+ completion?: number
+ [key: string]: unknown
+}
+
+interface AgAttributes {
+ metrics?: {
+ tokens?: {cumulative?: AgMetricBuckets; incremental?: AgMetricBuckets}
+ costs?: {cumulative?: AgMetricBuckets; incremental?: AgMetricBuckets}
+ duration?: {cumulative?: number}
+ [key: string]: unknown
+ }
+ data?: {
+ inputs?: unknown
+ outputs?: unknown
+ internals?: unknown
+ parameters?: unknown
+ [key: string]: unknown
+ }
+ meta?: {configuration?: unknown; [key: string]: unknown}
+ node?: {type?: string; [key: string]: unknown}
+ [key: string]: unknown
+}
+
+const getAg = (span?: TraceSpanNode) => span?.attributes?.ag as AgAttributes | undefined
+
// Metric extraction helpers ----------------------------------------------------
-const getTokenMetrics = (span?: TraceSpanNode) => span?.attributes?.ag?.metrics?.tokens ?? null
+const getTokenMetrics = (span?: TraceSpanNode) => getAg(span)?.metrics?.tokens ?? null
export const getTokens = (span?: TraceSpanNode) => {
const tokens = getTokenMetrics(span)
@@ -25,14 +54,14 @@ export const getCompletionTokens = (span?: TraceSpanNode) => {
}
export const getCost = (span?: TraceSpanNode) => {
- const costs = span?.attributes?.ag?.metrics?.costs
+ const costs = getAg(span)?.metrics?.costs
return costs?.cumulative?.total ?? costs?.incremental?.total ?? null
}
export const getLatency = (span?: TraceSpanNode) =>
- span?.attributes?.ag?.metrics?.duration?.cumulative ?? null
+ getAg(span)?.metrics?.duration?.cumulative ?? null
-export const getTraceInputs = (span?: TraceSpanNode) => span?.attributes?.ag?.data?.inputs ?? null
+export const getTraceInputs = (span?: TraceSpanNode) => getAg(span)?.data?.inputs ?? null
// A streamed agent run's ROOT span returns a generator, so its `ag.data.outputs` is the
// generator object's repr (``), not the reply — the span
@@ -40,7 +69,7 @@ export const getTraceInputs = (span?: TraceSpanNode) => span?.attributes?.ag?.da
// `agent`-type span (`invoke_agent`).
const GENERATOR_REPR = /^<(?:async_)?generator object/
-const spanOutputs = (span: TraceSpanNode): unknown => (span.attributes as any)?.ag?.data?.outputs
+const spanOutputs = (span: TraceSpanNode): unknown => getAg(span)?.data?.outputs
export const getTraceOutputs = (span?: TraceSpanNode): unknown => {
if (!span) return null
@@ -63,9 +92,9 @@ export const getTraceOutputs = (span?: TraceSpanNode): unknown => {
// General attribute helpers ----------------------------------------------------
export const getAgMetaConfiguration = (span?: TraceSpanNode) =>
- span?.attributes?.ag?.meta?.configuration ?? null
+ getAg(span)?.meta?.configuration ?? null
-export const getAgData = (span?: TraceSpanNode) => span?.attributes?.ag?.data ?? null
+export const getAgData = (span?: TraceSpanNode) => getAg(span)?.data ?? null
export const getAgDataInputs = (span?: TraceSpanNode) => getAgData(span)?.inputs ?? null
@@ -75,7 +104,7 @@ export const getAgDataInternals = (span?: TraceSpanNode) => getAgData(span)?.int
export const getAgDataParameters = (span?: TraceSpanNode) => getAgData(span)?.parameters ?? null
-export const getAgNodeType = (span?: TraceSpanNode) => span?.attributes?.ag?.node?.type ?? null
+export const getAgNodeType = (span?: TraceSpanNode) => getAg(span)?.node?.type ?? null
export const getSpanException = (span?: TraceSpanNode) =>
span?.events?.find((event) => event.name === "exception") ?? null
diff --git a/web/oss/src/state/newPlayground/workflowEntityBridge.ts b/web/oss/src/state/newPlayground/workflowEntityBridge.ts
index 0d23d413fe..9b55a1e902 100644
--- a/web/oss/src/state/newPlayground/workflowEntityBridge.ts
+++ b/web/oss/src/state/newPlayground/workflowEntityBridge.ts
@@ -193,9 +193,11 @@ const resolveRemainingWorkflowRevisionId = async ({
// Refetch variants list and try adjacent
const variantsQuery = store.get(workflowVariantsQueryAtomFamily(workflowId))
- if (variantsQuery.refetch) {
+ // The atom can return a plain placeholder (no refetch) before the scoped query is live
+ const refetchVariants = "refetch" in variantsQuery ? variantsQuery.refetch : undefined
+ if (refetchVariants) {
try {
- const refetched = await variantsQuery.refetch()
+ const refetched = await refetchVariants()
const refetchedVariantIds =
refetched.data?.workflow_variants
?.map((variant) => variant.id)
diff --git a/web/oss/src/state/org/index.ts b/web/oss/src/state/org/index.ts
index 205fbb5198..da8ca174c8 100644
--- a/web/oss/src/state/org/index.ts
+++ b/web/oss/src/state/org/index.ts
@@ -37,5 +37,5 @@ export const getOrgValues = () => {
export const resetOrganizationData = () => {
const store = getDefaultStore()
- store.set(resetOrganizationDataAtom, null)
+ store.set(resetOrganizationDataAtom)
}
diff --git a/web/oss/src/state/project/index.ts b/web/oss/src/state/project/index.ts
index 2c4dd8faa5..f7e41cdd00 100644
--- a/web/oss/src/state/project/index.ts
+++ b/web/oss/src/state/project/index.ts
@@ -38,5 +38,5 @@ export const getProjectValues = () => {
export const resetProjectData = () => {
const store = getDefaultStore()
- store.set(resetProjectDataAtom, null)
+ store.set(resetProjectDataAtom)
}
diff --git a/web/oss/src/state/project/selectors/project.ts b/web/oss/src/state/project/selectors/project.ts
index bf63053c29..24fbd70f2f 100644
--- a/web/oss/src/state/project/selectors/project.ts
+++ b/web/oss/src/state/project/selectors/project.ts
@@ -143,7 +143,10 @@ const _projectBelongsToWorkspace = (project: ProjectsResponse, workspaceId: stri
return false
}
-const projectMatchesWorkspace = (project: ProjectsResponse, workspaceId: string) => {
+const projectMatchesWorkspace = (
+ project: ProjectsResponse,
+ workspaceId: string | null | undefined,
+) => {
if (!workspaceId) return false
if (project.workspace_id && project.workspace_id === workspaceId) return true
if (project.organization_id && project.organization_id === workspaceId) return true
diff --git a/web/oss/src/state/url/auth.ts b/web/oss/src/state/url/auth.ts
index 2174bdb2db..14161b59f0 100644
--- a/web/oss/src/state/url/auth.ts
+++ b/web/oss/src/state/url/auth.ts
@@ -276,11 +276,13 @@ export const syncAuthStateFromUrl = (nextUrl?: string) => {
if (!inviteEmail || !userEmail || inviteEmail === userEmail) {
if (!isCurrentAcceptRouteForInvite(appState, invite)) {
- void Router.replace({pathname: "/workspaces/accept", query: invite}).catch(
- (error) => {
- console.error("Failed to redirect to invite acceptance:", error)
- },
- )
+ // Spread into a fresh object literal so it satisfies ParsedUrlQueryInput
+ void Router.replace({
+ pathname: "/workspaces/accept",
+ query: {...invite},
+ }).catch((error) => {
+ console.error("Failed to redirect to invite acceptance:", error)
+ })
}
store.set(protectedRouteReadyAtom, false)
return
diff --git a/web/oss/src/state/url/focusDrawer.ts b/web/oss/src/state/url/focusDrawer.ts
index 9bcfb3f5cb..7a5599ed92 100644
--- a/web/oss/src/state/url/focusDrawer.ts
+++ b/web/oss/src/state/url/focusDrawer.ts
@@ -88,7 +88,7 @@ export const syncFocusDrawerStateFromUrl = (nextUrl?: string) => {
(urlProvided && currentState.open && hasStoredTarget && !currentState.isClosing)
if (shouldReset) {
- store.set(resetAtom, null)
+ store.set(resetAtom)
}
})
return
diff --git a/web/oss/src/state/url/index.ts b/web/oss/src/state/url/index.ts
index 1b30c5f03e..6146076466 100644
--- a/web/oss/src/state/url/index.ts
+++ b/web/oss/src/state/url/index.ts
@@ -1,5 +1,4 @@
-import {getDefaultStore} from "jotai"
-import {eagerAtom} from "jotai-eager"
+import {atom, getDefaultStore} from "jotai"
import {appStateSnapshotAtom} from "@/oss/state/appState"
import {selectedOrgAtom} from "@/oss/state/org/selectors/org"
@@ -20,7 +19,8 @@ export interface URLState {
appURL: string
}
-export const urlAtom = eagerAtom((get) => {
+// Plain atom (not eagerAtom): all deps are synchronous, so the value is never a Promise.
+export const urlAtom = atom((get) => {
const snapshot = get(appStateSnapshotAtom)
const selectedOrg = get(selectedOrgAtom)
const recentlyVisitedAppId = get(recentAppIdAtom)
diff --git a/web/oss/src/state/url/playground.ts b/web/oss/src/state/url/playground.ts
index bf992fb315..3926788af9 100644
--- a/web/oss/src/state/url/playground.ts
+++ b/web/oss/src/state/url/playground.ts
@@ -21,7 +21,13 @@ import {
urlSnapshotController,
} from "@agenta/playground"
import type {PlaygroundSnapshot} from "@agenta/playground/snapshot"
-import {playgroundSnapshotController} from "@agenta/playground/state"
+import {
+ playgroundSnapshotController,
+ type EntityType,
+ type HydratedSnapshotEntity,
+ type RunnableType,
+ type SnapshotSelectionInput,
+} from "@agenta/playground/state"
import {atom, getDefaultStore} from "jotai"
import type {Store} from "jotai/vanilla/store"
@@ -200,26 +206,6 @@ const arraysEqual = (a: string[], b: string[]) => {
return true
}
-interface HydratedEntityDescriptor {
- id: string
- runnableType: RunnableType
- entityType?: string
- depth?: number
- label?: string
-}
-
-type RunnableType = "evaluator" | "workflow"
-
-type PlaygroundEntityType = "evaluator" | "workflow"
-
-interface SnapshotSelectionInput {
- id: string
- runnableType: RunnableType
- entityType?: PlaygroundEntityType
- depth?: number
- label?: string
-}
-
interface PlaygroundNode {
id: string
entityType: string
@@ -231,7 +217,8 @@ interface PlaygroundNode {
const entityTypeToRunnableType = (entityType: string | undefined): RunnableType | null => {
switch (entityType) {
case "evaluator":
- return "evaluator"
+ // Legacy snapshots may carry "evaluator"; typed as-is (package RunnableType is "workflow"-only)
+ return "evaluator" as RunnableType
case "workflow":
return "workflow"
default:
@@ -239,10 +226,11 @@ const entityTypeToRunnableType = (entityType: string | undefined): RunnableType
}
}
-const runnableTypeToEntityType = (runnableType: RunnableType): PlaygroundEntityType | null => {
+const runnableTypeToEntityType = (runnableType: string): EntityType | null => {
switch (runnableType) {
case "evaluator":
- return "evaluator"
+ // Legacy snapshots may carry "evaluator"; typed as-is (package EntityType has no "evaluator")
+ return "evaluator" as EntityType
case "workflow":
return "workflow"
default:
@@ -270,7 +258,7 @@ const buildSnapshotSelectionInputs = (
snapshotInputs.push({
id: rootEntityId,
runnableType,
- ...(node?.entityType ? {entityType: node.entityType as PlaygroundEntityType} : {}),
+ ...(node?.entityType ? {entityType: node.entityType} : {}),
...(hasDownstreamNodes ? {depth: 0} : {}),
...(node?.label ? {label: node.label} : {}),
})
@@ -285,7 +273,7 @@ const buildSnapshotSelectionInputs = (
snapshotInputs.push({
id: node.entityId,
runnableType,
- entityType: node.entityType as PlaygroundEntityType,
+ entityType: node.entityType,
depth: node.depth,
...(node.label ? {label: node.label} : {}),
})
@@ -473,7 +461,7 @@ export const updatePlaygroundUrlWithDrafts = () => {
const applyPlaygroundSelection = (
store: Store,
next: string[],
- hydratedEntities?: HydratedEntityDescriptor[],
+ hydratedEntities?: HydratedSnapshotEntity[],
options?: {skipInitialRow?: boolean},
): boolean => {
const sanitized = sanitizeRevisionList(next)
@@ -518,7 +506,7 @@ const applyPlaygroundSelection = (
rootHydratedEntities.find((entity) => entity.id === rootEntityIds[0]) ??
rootHydratedEntities[0]
const primaryEntityType =
- (primaryHydratedEntity?.entityType as PlaygroundEntityType | undefined) ??
+ (primaryHydratedEntity?.entityType as EntityType | undefined) ??
(primaryHydratedEntity
? runnableTypeToEntityType(primaryHydratedEntity.runnableType)
: null) ??
@@ -568,7 +556,7 @@ const applyPlaygroundSelection = (
for (const downstream of downstreamHydratedEntities) {
const depth = downstream.depth ?? 1
const entityType =
- (downstream.entityType as PlaygroundEntityType | undefined) ??
+ (downstream.entityType as EntityType | undefined) ??
runnableTypeToEntityType(downstream.runnableType)
if (!entityType) continue
@@ -679,10 +667,11 @@ export const syncPlaygroundStateFromUrl = (nextUrl?: string) => {
hydrateResult.loadable || hydrateResult.localTestset,
)
+ const hydratedSelection = hydrateResult.selection
const selectionChanged = applyPlaygroundSelection(
store,
- hydrateResult.selection,
- hydrateResult.entities as HydratedEntityDescriptor[] | undefined,
+ hydratedSelection,
+ hydrateResult.entities,
hasLoadableRestore ? {skipInitialRow: true} : undefined,
)
@@ -705,7 +694,7 @@ export const syncPlaygroundStateFromUrl = (nextUrl?: string) => {
skipUrlRevisionsUntilUpdate = true
// Update URL with new selection (deferred to avoid sync loop)
requestAnimationFrame(() => {
- writePlaygroundSelectionToQuery(hydrateResult.selection)
+ writePlaygroundSelectionToQuery(hydratedSelection)
skipUrlRevisionsUntilUpdate = false
})
}
diff --git a/web/oss/src/state/workspace/atoms/mutations.ts b/web/oss/src/state/workspace/atoms/mutations.ts
index b146ac75c9..2bf16a2dc6 100644
--- a/web/oss/src/state/workspace/atoms/mutations.ts
+++ b/web/oss/src/state/workspace/atoms/mutations.ts
@@ -23,7 +23,8 @@ export const updateWorkspaceNameAtom = atomWithMutation<
// Update both workspace and organization in parallel
await Promise.all([
updateWorkspace({organizationId, workspaceId, name}),
- updateOrganization(organizationId, name),
+ // typed as-is (latent bug): sends the bare name string, not {name}, as the PATCH body
+ updateOrganization(organizationId, name as unknown as {name: string}),
])
},
onSuccess: (_, {name, organizationId}) => {
diff --git a/web/oss/tsconfig.json b/web/oss/tsconfig.json
index dfe57cd1b2..80e4dfec6a 100644
--- a/web/oss/tsconfig.json
+++ b/web/oss/tsconfig.json
@@ -23,6 +23,7 @@
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": [
"node_modules",
+ "tests",
"src/components/OldPlayground/**/*.tsx",
"src/**/*.test.ts",
"src/**/*.test.tsx"
diff --git a/web/packages/agenta-entities/src/shared/paginated/createPaginatedEntityStore.ts b/web/packages/agenta-entities/src/shared/paginated/createPaginatedEntityStore.ts
index 6d8cca2c68..0887813a3b 100644
--- a/web/packages/agenta-entities/src/shared/paginated/createPaginatedEntityStore.ts
+++ b/web/packages/agenta-entities/src/shared/paginated/createPaginatedEntityStore.ts
@@ -253,7 +253,7 @@ export interface PaginatedEntityStore<
*
* @deprecated Use `actions.refresh` instead for consistency
*/
- refreshAtom: WritableAtom
+ refreshAtom: WritableAtom number)], void>
/**
* Meta atom providing the query parameters.
@@ -309,7 +309,7 @@ export interface PaginatedEntityStore<
/**
* Refresh the paginated data
*/
- refresh: WritableAtom
+ refresh: WritableAtom number)], void>
}
/**
@@ -390,11 +390,11 @@ export function createPaginatedEntityStore<
excludeRowIdsAtom,
})
- // Create writable refresh atom
+ // Create writable refresh atom (setState-style value/updater optional; bare set() bumps the counter)
const refreshAtom = atom(
(get) => get(internalRefreshAtom),
- (_get, set) => {
- set(internalRefreshAtom, (prev) => prev + 1)
+ (_get, set, next?: number | ((prev: number) => number)) => {
+ set(internalRefreshAtom, next ?? ((prev: number) => prev + 1))
},
)
diff --git a/web/packages/agenta-entities/src/workflow/index.ts b/web/packages/agenta-entities/src/workflow/index.ts
index 409b879943..1492ad8442 100644
--- a/web/packages/agenta-entities/src/workflow/index.ts
+++ b/web/packages/agenta-entities/src/workflow/index.ts
@@ -170,6 +170,7 @@ export {
queryWorkflowRevisionsByWorkflow,
queryWorkflowRevisionsByWorkflows,
queryWorkflowRevisions,
+ type WorkflowRevisionWindowing,
// Retrieve (single revision by ref — slug/version/id)
retrieveWorkflowRevision,
// Fetch (single revision by ID)
diff --git a/web/packages/agenta-entity-ui/src/variant/components/EnvironmentStatus.tsx b/web/packages/agenta-entity-ui/src/variant/components/EnvironmentStatus.tsx
index a6d3bb10c9..212ac75f01 100644
--- a/web/packages/agenta-entity-ui/src/variant/components/EnvironmentStatus.tsx
+++ b/web/packages/agenta-entity-ui/src/variant/components/EnvironmentStatus.tsx
@@ -14,7 +14,8 @@ export const statusMap: Record = {
}
const EnvironmentStatus: FC<{
- variant: Pick
+ /** `id` is optional — without it the revision-deployment fallback lookup is skipped */
+ variant: Pick & Partial>
className?: string
}> = ({variant, className}) => {
// Fallback to environment entity if deployedIn is not embedded on the variant
diff --git a/web/packages/agenta-ui/src/Editor/index.ts b/web/packages/agenta-ui/src/Editor/index.ts
index 66c1383987..ef289882b8 100644
--- a/web/packages/agenta-ui/src/Editor/index.ts
+++ b/web/packages/agenta-ui/src/Editor/index.ts
@@ -63,6 +63,7 @@ export {
ON_CHANGE_LANGUAGE,
PropertyClickPlugin,
} from "./plugins/code"
+export type {CodeLanguage} from "./plugins/code/types"
export {$getEditorCodeAsString, constructJsonFromSchema} from "./plugins/code/utils/editorCodeUtils"
export {$isCodeBlockNode, $createCodeBlockNode} from "./plugins/code/nodes/CodeBlockNode"
export {$createCodeLineNode} from "./plugins/code/nodes/CodeLineNode"
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/columns/createStandardColumns.tsx b/web/packages/agenta-ui/src/InfiniteVirtualTable/columns/createStandardColumns.tsx
index fc9c997b31..f87ae9b0cf 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/columns/createStandardColumns.tsx
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/columns/createStandardColumns.tsx
@@ -53,6 +53,8 @@ export interface TextColumnDef {
fixed?: "left" | "right"
/** Lock column from being hidden in visibility menu (defaults to true if fixed is set) */
columnVisibilityLocked?: boolean
+ /** Custom value extractor for CSV export (read by useTableExport) */
+ exportValue?: (row: T, column?: ColumnsType[number], columnIndex?: number) => unknown
}
export interface DateColumnDef {
@@ -171,6 +173,7 @@ function createTextColumn(def: TextColumnDef): ColumnType