Files
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

263 lines
8.1 KiB
TypeScript

import { db } from '@sim/db'
import { workflowExecutionLogs } from '@sim/db/schema'
import { isRecordLike } from '@sim/utils/object'
import { and, desc, eq, or, sql } from 'drizzle-orm'
import { materializeExecutionData, TRACE_STORE_REF_KEY } from '@/lib/logs/execution/trace-store'
import type { SerializableExecutionState } from '@/executor/execution/types'
import {
isResolvedSecretTraceProvenanceV1,
type ResolvedSecretTraceProvenanceV1,
} from '@/executor/utils/resolved-secret-trace-registry'
const LATEST_EXECUTION_STATE_CANDIDATE_LIMIT = 10
interface ExecutionStateRecord {
executionId: string
state: SerializableExecutionState
}
function isSerializableExecutionState(value: unknown): value is SerializableExecutionState {
if (!value || typeof value !== 'object') return false
const state = value as Record<string, unknown>
return (
typeof state.blockStates === 'object' &&
Array.isArray(state.executedBlocks) &&
Array.isArray(state.blockLogs) &&
typeof state.decisions === 'object' &&
Array.isArray(state.completedLoops) &&
Array.isArray(state.activeExecutionPath)
)
}
function extractExecutionState(executionData: unknown): SerializableExecutionState | null {
if (!executionData || typeof executionData !== 'object') return null
const state = (executionData as Record<string, unknown>).executionState
return isSerializableExecutionState(state) ? state : null
}
function extractLegacyWorkflowInput(executionData: Record<string, unknown>): unknown | undefined {
if (!isRecordLike(executionData.executionState)) return undefined
const { blockStates } = executionData.executionState
if (!isRecordLike(blockStates)) return undefined
for (const state of Object.values(blockStates)) {
if (
isRecordLike(state) &&
state.executed === false &&
state.executionTime === 0 &&
state.output != null
) {
return state.output
}
}
return undefined
}
interface ExecutionStateRow {
executionId: string
workflowId: string | null
workspaceId: string
status?: string
executionData: unknown
}
interface TrustedWorkflowToolExecutionBase {
executionId: string
workflowId: string
status: 'completed' | 'failed' | 'cancelled'
}
export interface TrustedWorkflowToolExecutionWithoutContent
extends TrustedWorkflowToolExecutionBase {
contentAvailable: false
}
export interface TrustedWorkflowToolExecutionWithContent extends TrustedWorkflowToolExecutionBase {
contentAvailable: true
finalOutput?: unknown
error?: string
blockLogs: SerializableExecutionState['blockLogs']
provenance: ResolvedSecretTraceProvenanceV1
}
export type TrustedWorkflowToolExecution =
| TrustedWorkflowToolExecutionWithoutContent
| TrustedWorkflowToolExecutionWithContent
async function getExecutionStateRow(
executionId: string,
workflowId: string
): Promise<ExecutionStateRow | undefined> {
const [row] = await db
.select({
executionId: workflowExecutionLogs.executionId,
workflowId: workflowExecutionLogs.workflowId,
workspaceId: workflowExecutionLogs.workspaceId,
status: workflowExecutionLogs.status,
executionData: workflowExecutionLogs.executionData,
})
.from(workflowExecutionLogs)
.where(
and(
eq(workflowExecutionLogs.executionId, executionId),
eq(workflowExecutionLogs.workflowId, workflowId)
)
)
.limit(1)
return row
}
async function materializeExecutionDataFromRow(
row: ExecutionStateRow | undefined
): Promise<Record<string, unknown> | null> {
if (!row) return null
return materializeExecutionData(row.executionData as Record<string, unknown> | null, {
workspaceId: row.workspaceId,
workflowId: row.workflowId,
executionId: row.executionId,
})
}
async function extractExecutionStateFromRow(
row: ExecutionStateRow | undefined
): Promise<SerializableExecutionState | null> {
const executionData = await materializeExecutionDataFromRow(row)
return extractExecutionState(executionData)
}
export async function getExecutionStateForWorkflow(
executionId: string,
workflowId: string
): Promise<SerializableExecutionState | null> {
const row = await getExecutionStateRow(executionId, workflowId)
return extractExecutionStateFromRow(row)
}
/** Loads a terminal workflow result only when its server-persisted Copilot binding matches. */
export async function getTrustedWorkflowToolExecution(
executionId: string,
workflowId: string,
copilotToolCallId: string
): Promise<TrustedWorkflowToolExecution | null> {
const row = await getExecutionStateRow(executionId, workflowId)
if (
!row ||
(row.status !== 'completed' && row.status !== 'failed' && row.status !== 'cancelled')
) {
return null
}
const executionData = await materializeExecutionDataFromRow(row)
const state = extractExecutionState(executionData)
const provenance = state?.resolvedSecretTraceProvenance
const topLevelCorrelation = executionData?.correlation
const triggerCorrelation = isRecordLike(executionData?.trigger)
? executionData.trigger.data
: undefined
const correlation = isRecordLike(topLevelCorrelation)
? topLevelCorrelation
: isRecordLike(triggerCorrelation) && isRecordLike(triggerCorrelation.correlation)
? triggerCorrelation.correlation
: undefined
if (
!executionData ||
!isRecordLike(correlation) ||
correlation.copilotToolCallId !== copilotToolCallId
) {
return null
}
if (!state || !isResolvedSecretTraceProvenanceV1(provenance)) {
return {
executionId,
workflowId,
status: row.status,
contentAvailable: false,
}
}
return {
executionId,
workflowId,
status: row.status,
contentAvailable: true,
...(Object.hasOwn(executionData, 'finalOutput')
? { finalOutput: executionData.finalOutput }
: {}),
...(typeof executionData.error === 'string' ? { error: executionData.error } : {}),
blockLogs: state.blockLogs,
provenance,
}
}
/**
* Returns the workflow input recorded for a past execution so a new run can
* reuse it by reference. `found` distinguishes a missing execution from an
* execution that recorded no input.
*/
export async function getExecutionInputForWorkflow(
executionId: string,
workflowId: string
): Promise<{ found: boolean; input?: unknown }> {
const row = await getExecutionStateRow(executionId, workflowId)
if (!row) {
return { found: false }
}
const data = await materializeExecutionDataFromRow(row)
if (!data) return { found: true }
if (Object.hasOwn(data, 'workflowInput')) {
return { found: true, input: data.workflowInput }
}
return { found: true, input: extractLegacyWorkflowInput(data) }
}
export async function getLatestExecutionStateWithExecutionId(
workflowId: string
): Promise<ExecutionStateRecord | null> {
const rows = await db
.select({
executionId: workflowExecutionLogs.executionId,
workflowId: workflowExecutionLogs.workflowId,
workspaceId: workflowExecutionLogs.workspaceId,
executionState: sql<unknown>`${workflowExecutionLogs.executionData} -> 'executionState'`,
traceStoreRef: sql<unknown>`${workflowExecutionLogs.executionData} -> ${TRACE_STORE_REF_KEY}`,
})
.from(workflowExecutionLogs)
.where(
and(
eq(workflowExecutionLogs.workflowId, workflowId),
or(
sql`${workflowExecutionLogs.executionData} -> 'executionState' IS NOT NULL`,
sql`${workflowExecutionLogs.executionData} -> ${TRACE_STORE_REF_KEY} IS NOT NULL`
)
)
)
.orderBy(desc(workflowExecutionLogs.startedAt))
.limit(LATEST_EXECUTION_STATE_CANDIDATE_LIMIT)
for (const row of rows) {
const state = await extractExecutionStateFromRow({
executionId: row.executionId,
workflowId: row.workflowId,
workspaceId: row.workspaceId,
executionData: {
...(row.executionState !== null && row.executionState !== undefined
? { executionState: row.executionState }
: {}),
...(row.traceStoreRef !== null && row.traceStoreRef !== undefined
? { [TRACE_STORE_REF_KEY]: row.traceStoreRef }
: {}),
},
})
if (state) return { executionId: row.executionId, state }
}
return null
}