fix: harden presentation demo fact projections
This commit is contained in:
@@ -90,7 +90,7 @@ describe("WorkflowGraphStage", () => {
|
|||||||
selectedNodeId={null}
|
selectedNodeId={null}
|
||||||
selectNode={vi.fn()}
|
selectNode={vi.fn()}
|
||||||
variant="compact"
|
variant="compact"
|
||||||
proof={{ runId: "run_recorded_lda_report", traceLabel: "5 workflow nodes", evidenceLabel: "JSON-RPC evidence" }}
|
proof={{ runId: "run_recorded_lda_report", traceLabel: "9 workflow nodes", evidenceLabel: "JSON-RPC evidence" }}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import { initialDemoTimelineState } from "../demo/timeline/reducer.js";
|
|||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
import { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js";
|
import { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js";
|
||||||
|
|
||||||
const controller = (): DemoTimelineController => {
|
const controller = (eventMode: "recorded" | "empty" = "recorded"): DemoTimelineController => {
|
||||||
const recording = loadCanonicalDemoRecording();
|
const recording = loadCanonicalDemoRecording();
|
||||||
return {
|
return {
|
||||||
state: {
|
state: {
|
||||||
...initialDemoTimelineState,
|
...initialDemoTimelineState,
|
||||||
mode: "replay",
|
mode: "replay",
|
||||||
phase: "paused",
|
phase: "paused",
|
||||||
events: recording.events,
|
events: eventMode === "recorded" ? recording.events : [],
|
||||||
appliedCount: recording.events.length,
|
appliedCount: eventMode === "recorded" ? recording.events.length : 0,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
},
|
},
|
||||||
inFlight: false,
|
inFlight: false,
|
||||||
@@ -55,4 +55,16 @@ describe("projectDemoLifecycleFacts", () => {
|
|||||||
expect(facts.run.id).toBe("run_recorded_lda_report");
|
expect(facts.run.id).toBe("run_recorded_lda_report");
|
||||||
expect(facts.run.status).toBe("interrupted");
|
expect(facts.run.status).toBe("interrupted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back honestly when replay evidence has not loaded yet", () => {
|
||||||
|
const facts = projectDemoLifecycleFacts(controller("empty"));
|
||||||
|
|
||||||
|
expect(facts.artifact).toEqual({ id: "unavailable", version: null });
|
||||||
|
expect(facts.deployment).toEqual({
|
||||||
|
id: "unavailable",
|
||||||
|
driftPolicy: "unavailable",
|
||||||
|
bindings: [],
|
||||||
|
});
|
||||||
|
expect(facts.run).toEqual({ id: null, status: "not started" });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
|
import type { DemoEvent } from "../demo/timeline/models.js";
|
||||||
|
|
||||||
export type DemoLifecycleFacts = {
|
export type DemoLifecycleFacts = {
|
||||||
readonly draft: {
|
readonly draft: {
|
||||||
@@ -21,12 +22,25 @@ export type DemoLifecycleFacts = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deploymentInspect = (demo: DemoTimelineController) =>
|
const deploymentInspect = (demo: DemoTimelineController): DemoEvent | undefined =>
|
||||||
demo.state.events.find((event) => event.stage === "deployment_check");
|
demo.state.events.find((event) => event.stage === "deployment_check");
|
||||||
|
|
||||||
const runStart = (demo: DemoTimelineController) =>
|
const runStart = (demo: DemoTimelineController): DemoEvent | undefined =>
|
||||||
demo.state.events.find((event) => event.stage === "run_start");
|
demo.state.events.find((event) => event.stage === "run_start");
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
|
||||||
|
const stringField = (record: Record<string, unknown> | undefined, field: string): string | undefined => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return typeof value === "string" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberField = (record: Record<string, unknown> | undefined, field: string): number | undefined => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return typeof value === "number" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]> => {
|
const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]> => {
|
||||||
if (!Array.isArray(value)) return [];
|
if (!Array.isArray(value)) return [];
|
||||||
return value.flatMap((entry) => {
|
return value.flatMap((entry) => {
|
||||||
@@ -42,19 +56,9 @@ const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]>
|
|||||||
*/
|
*/
|
||||||
export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLifecycleFacts => {
|
export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLifecycleFacts => {
|
||||||
const deployment = deploymentInspect(demo);
|
const deployment = deploymentInspect(demo);
|
||||||
const deploymentInterpreted = deployment?.interpreted as
|
const deploymentInterpreted = isRecord(deployment?.interpreted) ? deployment.interpreted : undefined;
|
||||||
| {
|
|
||||||
id?: string;
|
|
||||||
artifactId?: string;
|
|
||||||
artifactVersion?: number;
|
|
||||||
driftPolicy?: string;
|
|
||||||
bindings?: unknown;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
const run = runStart(demo);
|
const run = runStart(demo);
|
||||||
const runInterpreted = run?.interpreted as
|
const runInterpreted = isRecord(run?.interpreted) ? run.interpreted : undefined;
|
||||||
| { runId?: string; status?: string }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
draft: {
|
draft: {
|
||||||
@@ -63,19 +67,17 @@ export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLif
|
|||||||
status: "prepared context",
|
status: "prepared context",
|
||||||
},
|
},
|
||||||
artifact: {
|
artifact: {
|
||||||
id: deploymentInterpreted?.artifactId ?? "unavailable",
|
id: stringField(deploymentInterpreted, "artifactId") ?? "unavailable",
|
||||||
version: typeof deploymentInterpreted?.artifactVersion === "number"
|
version: numberField(deploymentInterpreted, "artifactVersion") ?? null,
|
||||||
? deploymentInterpreted.artifactVersion
|
|
||||||
: null,
|
|
||||||
},
|
},
|
||||||
deployment: {
|
deployment: {
|
||||||
id: deploymentInterpreted?.id ?? "unavailable",
|
id: stringField(deploymentInterpreted, "id") ?? "unavailable",
|
||||||
driftPolicy: deploymentInterpreted?.driftPolicy ?? "unavailable",
|
driftPolicy: stringField(deploymentInterpreted, "driftPolicy") ?? "unavailable",
|
||||||
bindings: readBindings(deploymentInterpreted?.bindings),
|
bindings: readBindings(deploymentInterpreted?.["bindings"]),
|
||||||
},
|
},
|
||||||
run: {
|
run: {
|
||||||
id: runInterpreted?.runId ?? run?.resultingIds.runId ?? null,
|
id: stringField(runInterpreted, "runId") ?? run?.resultingIds.runId ?? null,
|
||||||
status: runInterpreted?.status ?? "not started",
|
status: stringField(runInterpreted, "status") ?? "not started",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -116,6 +116,53 @@ describe("demo-run-facts", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back honestly before any replay events have been applied", () => {
|
||||||
|
const facts = projectDemoRunFacts(controller({
|
||||||
|
state: {
|
||||||
|
...initialDemoTimelineState,
|
||||||
|
mode: "replay",
|
||||||
|
phase: "ready",
|
||||||
|
events: [],
|
||||||
|
appliedCount: 0,
|
||||||
|
autoplay: false,
|
||||||
|
},
|
||||||
|
interruptPayload: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(facts.input).toEqual({ selectedDocuments: [], boardPath: "" });
|
||||||
|
expect(facts.interrupt).toMatchObject({
|
||||||
|
kind: "unknown",
|
||||||
|
typed: false,
|
||||||
|
outcomes: [],
|
||||||
|
proposedIssues: [],
|
||||||
|
reportMarkdownPreview: "",
|
||||||
|
});
|
||||||
|
expect(facts.resume).toEqual({ outcome: null, payload: null });
|
||||||
|
expect(facts.output.state).toBe("not-created");
|
||||||
|
expect(facts.trace.frames).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores malformed output evidence instead of displaying partial data", () => {
|
||||||
|
const recording = loadCanonicalDemoRecording();
|
||||||
|
const malformedEvents = recording.events.map((event) =>
|
||||||
|
event.stage === "run_resume"
|
||||||
|
? { ...event, interpreted: { output: { markdown: 42 } } }
|
||||||
|
: event,
|
||||||
|
);
|
||||||
|
const facts = projectDemoRunFacts(controller({
|
||||||
|
state: {
|
||||||
|
...initialDemoTimelineState,
|
||||||
|
mode: "replay",
|
||||||
|
phase: "completed",
|
||||||
|
events: malformedEvents,
|
||||||
|
appliedCount: malformedEvents.length,
|
||||||
|
autoplay: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(facts.output.state).toBe("not-created");
|
||||||
|
});
|
||||||
|
|
||||||
it("projects trace frames and empty object state accurately", () => {
|
it("projects trace frames and empty object state accurately", () => {
|
||||||
const recording = loadCanonicalDemoRecording();
|
const recording = loadCanonicalDemoRecording();
|
||||||
const facts = projectDemoRunFacts(controller({
|
const facts = projectDemoRunFacts(controller({
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import * as v from "valibot";
|
||||||
|
import {
|
||||||
|
LdaReportInterruptPayloadSchema,
|
||||||
|
LdaReportOutputSchema,
|
||||||
|
type LdaReportInterruptPayload,
|
||||||
|
type LdaReportOutput,
|
||||||
|
} from "../demo/ldaReportDemoModels.js";
|
||||||
|
import type { DemoEvent, DemoEventStage } from "../demo/timeline/models.js";
|
||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
import type { LdaReportOutput } from "../demo/ldaReportDemoModels.js";
|
|
||||||
import type { TraceFrame } from "../lifecycle/models.js";
|
|
||||||
|
|
||||||
export type RunFactsInput = {
|
export type RunFactsInput = {
|
||||||
readonly selectedDocuments: ReadonlyArray<string>;
|
readonly selectedDocuments: ReadonlyArray<string>;
|
||||||
@@ -56,6 +62,47 @@ export type DemoRunFacts = {
|
|||||||
|
|
||||||
const EMPTY_OBJECT_LABEL = "captured as empty object";
|
const EMPTY_OBJECT_LABEL = "captured as empty object";
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
|
||||||
|
const recordField = (
|
||||||
|
record: Record<string, unknown> | undefined,
|
||||||
|
field: string,
|
||||||
|
): Record<string, unknown> | undefined => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return isRecord(value) ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stringField = (record: Record<string, unknown> | undefined, field: string): string | undefined => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return typeof value === "string" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const booleanField = (record: Record<string, unknown> | undefined, field: string): boolean | undefined => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return typeof value === "boolean" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stringArrayField = (
|
||||||
|
record: Record<string, unknown> | undefined,
|
||||||
|
field: string,
|
||||||
|
): ReadonlyArray<string> => {
|
||||||
|
const value = record?.[field];
|
||||||
|
return Array.isArray(value) && value.every((item) => typeof item === "string")
|
||||||
|
? value
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseInterruptPayload = (value: unknown): LdaReportInterruptPayload | null => {
|
||||||
|
const decoded = v.safeParse(LdaReportInterruptPayloadSchema, value);
|
||||||
|
return decoded.success ? decoded.output : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseReportOutput = (value: unknown): LdaReportOutput | null => {
|
||||||
|
const decoded = v.safeParse(LdaReportOutputSchema, value);
|
||||||
|
return decoded.success ? decoded.output : null;
|
||||||
|
};
|
||||||
|
|
||||||
export const formatFactValue = (value: unknown, absentLabel: string): string => {
|
export const formatFactValue = (value: unknown, absentLabel: string): string => {
|
||||||
if (value === undefined || value === null) return absentLabel;
|
if (value === undefined || value === null) return absentLabel;
|
||||||
if (typeof value === "object" && Object.keys(value).length === 0) return EMPTY_OBJECT_LABEL;
|
if (typeof value === "object" && Object.keys(value).length === 0) return EMPTY_OBJECT_LABEL;
|
||||||
@@ -63,57 +110,55 @@ export const formatFactValue = (value: unknown, absentLabel: string): string =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const findEvent = (
|
const findEvent = (
|
||||||
events: ReadonlyArray<{ readonly stage: string }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
stage: string,
|
stage: DemoEventStage,
|
||||||
): typeof events[number] | undefined =>
|
): DemoEvent | undefined =>
|
||||||
events.find((event) => event.stage === stage);
|
events.find((event) => event.stage === stage);
|
||||||
|
|
||||||
|
const eventParams = (
|
||||||
|
events: ReadonlyArray<DemoEvent>,
|
||||||
|
stage: DemoEventStage,
|
||||||
|
): Record<string, unknown> | undefined => {
|
||||||
|
const params = findEvent(events, stage)?.params;
|
||||||
|
return isRecord(params) ? params : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventInterpreted = (
|
||||||
|
events: ReadonlyArray<DemoEvent>,
|
||||||
|
stage: DemoEventStage,
|
||||||
|
): Record<string, unknown> | undefined => {
|
||||||
|
const interpreted = findEvent(events, stage)?.interpreted;
|
||||||
|
return isRecord(interpreted) ? interpreted : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const readWorkflowInput = (
|
const readWorkflowInput = (
|
||||||
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
): RunFactsInput => {
|
): RunFactsInput => {
|
||||||
const runStart = findEvent(events, "run_start") as
|
const wi = recordField(eventParams(events, "run_start"), "workflow_input");
|
||||||
| { params: { workflow_input?: { selected_documents?: unknown; board_path?: unknown } } }
|
|
||||||
| undefined;
|
|
||||||
const wi = runStart?.params?.workflow_input;
|
|
||||||
return {
|
return {
|
||||||
selectedDocuments: Array.isArray(wi?.selected_documents)
|
selectedDocuments: stringArrayField(wi, "selected_documents"),
|
||||||
? (wi.selected_documents as ReadonlyArray<string>)
|
boardPath: stringField(wi, "board_path") ?? "",
|
||||||
: [],
|
|
||||||
boardPath: typeof wi?.board_path === "string" ? wi.board_path : "",
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const readInterruptFacts = (
|
const readInterruptFacts = (
|
||||||
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown; readonly interpreted: unknown }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
interruptPayload: DemoTimelineController["interruptPayload"],
|
interruptPayload: DemoTimelineController["interruptPayload"],
|
||||||
): RunFactsInterrupt => {
|
): RunFactsInterrupt => {
|
||||||
const runStart = findEvent(events, "run_start") as
|
const runStartInterpreted = eventInterpreted(events, "run_start");
|
||||||
| {
|
const interruptInterpreted = eventInterpreted(events, "interrupt");
|
||||||
interpreted: {
|
const ri = recordField(runStartInterpreted, "interrupt");
|
||||||
interrupt?: {
|
const payload =
|
||||||
kind?: string;
|
interruptPayload ??
|
||||||
typed?: boolean;
|
parseInterruptPayload(interruptInterpreted?.["payload"]) ??
|
||||||
outcomes?: unknown;
|
parseInterruptPayload(ri?.["payload"]);
|
||||||
payload?: DemoTimelineController["interruptPayload"];
|
const outcomes = stringArrayField(interruptInterpreted, "outcomes").length > 0
|
||||||
};
|
? stringArrayField(interruptInterpreted, "outcomes")
|
||||||
};
|
: stringArrayField(ri, "outcomes");
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
const interruptEvent = findEvent(events, "interrupt") as
|
|
||||||
| { interpreted: { outcomes?: unknown; payload?: DemoTimelineController["interruptPayload"] } }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const ri = runStart?.interpreted?.interrupt;
|
|
||||||
const payload = interruptPayload ?? interruptEvent?.interpreted?.payload ?? ri?.payload ?? null;
|
|
||||||
const outcomes = Array.isArray(interruptEvent?.interpreted?.outcomes)
|
|
||||||
? (interruptEvent!.interpreted.outcomes as ReadonlyArray<string>)
|
|
||||||
: Array.isArray(ri?.outcomes)
|
|
||||||
? (ri.outcomes as ReadonlyArray<string>)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: typeof ri?.kind === "string" ? ri.kind : "unknown",
|
kind: stringField(ri, "kind") ?? "unknown",
|
||||||
typed: ri?.typed === true,
|
typed: booleanField(ri, "typed") === true,
|
||||||
outcomes,
|
outcomes,
|
||||||
proposedIssues: payload?.proposed_issues ?? [],
|
proposedIssues: payload?.proposed_issues ?? [],
|
||||||
reportMarkdownPreview: payload?.report_markdown ?? "",
|
reportMarkdownPreview: payload?.report_markdown ?? "",
|
||||||
@@ -121,41 +166,33 @@ const readInterruptFacts = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const readResumeFacts = (
|
const readResumeFacts = (
|
||||||
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
): RunFactsResume => {
|
): RunFactsResume => {
|
||||||
const runResume = findEvent(events, "run_resume") as
|
const params = eventParams(events, "run_resume");
|
||||||
| { params: { resume_payload?: unknown; resume_outcome?: string } }
|
if (!params) return { outcome: null, payload: null };
|
||||||
| undefined;
|
|
||||||
if (!runResume) return { outcome: null, payload: null };
|
|
||||||
const outcome =
|
const outcome =
|
||||||
runResume.params.resume_outcome === "submitted" ||
|
params.resume_outcome === "submitted" ||
|
||||||
runResume.params.resume_outcome === "cancelled"
|
params.resume_outcome === "cancelled"
|
||||||
? runResume.params.resume_outcome
|
? params.resume_outcome
|
||||||
: null;
|
: null;
|
||||||
const payload =
|
const payload =
|
||||||
typeof runResume.params.resume_payload === "object" &&
|
isRecord(params.resume_payload)
|
||||||
runResume.params.resume_payload !== null
|
? params.resume_payload
|
||||||
? (runResume.params.resume_payload as Record<string, unknown>)
|
|
||||||
: null;
|
: null;
|
||||||
if (outcome === null) return { outcome: null, payload: null };
|
if (outcome === null) return { outcome: null, payload: null };
|
||||||
return { outcome, payload } as RunFactsResume;
|
return { outcome, payload: payload ?? {} };
|
||||||
};
|
};
|
||||||
|
|
||||||
const readOutputFacts = (
|
const readOutputFacts = (
|
||||||
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
): RunFactsOutput => {
|
): RunFactsOutput => {
|
||||||
const resumeEvent = findEvent(events, "run_resume") as
|
const resumeInterpreted = eventInterpreted(events, "run_resume");
|
||||||
| { interpreted: { output?: unknown } }
|
const completedInterpreted = eventInterpreted(events, "completed");
|
||||||
| undefined;
|
|
||||||
const completedEvent = findEvent(events, "completed") as
|
|
||||||
| { interpreted: { output?: unknown } }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const raw = resumeEvent?.interpreted?.output ?? completedEvent?.interpreted?.output;
|
const output = parseReportOutput(resumeInterpreted?.["output"] ?? completedInterpreted?.["output"]);
|
||||||
if (!raw || typeof raw !== "object") {
|
if (!output) {
|
||||||
return { state: "not-created", message: "Output not created yet" };
|
return { state: "not-created", message: "Output not created yet" };
|
||||||
}
|
}
|
||||||
const output = raw as LdaReportOutput;
|
|
||||||
return {
|
return {
|
||||||
state: "created",
|
state: "created",
|
||||||
output,
|
output,
|
||||||
@@ -168,30 +205,33 @@ const formatRecord = (record: Record<string, unknown>, absentLabel: string): str
|
|||||||
formatFactValue(record, absentLabel);
|
formatFactValue(record, absentLabel);
|
||||||
|
|
||||||
const readTraceFacts = (
|
const readTraceFacts = (
|
||||||
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>,
|
events: ReadonlyArray<DemoEvent>,
|
||||||
): RunFactsTrace => {
|
): RunFactsTrace => {
|
||||||
const traceEvent = findEvent(events, "trace_read") as
|
const traceInterpreted = eventInterpreted(events, "trace_read");
|
||||||
| { interpreted: { frames?: unknown } }
|
const completedInterpreted = eventInterpreted(events, "completed");
|
||||||
| undefined;
|
const completedTrace = recordField(completedInterpreted, "trace");
|
||||||
const completedEvent = findEvent(events, "completed") as
|
|
||||||
| { interpreted: { trace?: { frames?: unknown } } }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const rawFrames =
|
const rawFrames =
|
||||||
traceEvent?.interpreted?.frames ??
|
traceInterpreted?.["frames"] ??
|
||||||
completedEvent?.interpreted?.trace?.frames;
|
completedTrace?.["frames"];
|
||||||
|
|
||||||
if (!Array.isArray(rawFrames)) return { frames: [] };
|
if (!Array.isArray(rawFrames)) return { frames: [] };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frames: rawFrames.map((frame: TraceFrame) => ({
|
frames: rawFrames.flatMap((frame) => {
|
||||||
nodeId: frame.nodeId,
|
if (!isRecord(frame)) return [];
|
||||||
stepType: frame.stepType,
|
return [{
|
||||||
outcome: frame.outcome,
|
nodeId: stringField(frame, "nodeId") ?? "unknown",
|
||||||
resolvedInputLabel: formatRecord(frame.resolvedInput, "not captured in this recording"),
|
stepType: stringField(frame, "stepType") ?? "unknown",
|
||||||
outputLabel: formatRecord(frame.output, "not captured in this recording"),
|
outcome: stringField(frame, "outcome") ?? "unknown",
|
||||||
stateChangesLabel: formatRecord(frame.stateChanges, "not captured in this recording"),
|
resolvedInputLabel: formatRecord(
|
||||||
})),
|
recordField(frame, "resolvedInput") ?? {},
|
||||||
|
"not captured in this recording",
|
||||||
|
),
|
||||||
|
outputLabel: formatRecord(recordField(frame, "output") ?? {}, "not captured in this recording"),
|
||||||
|
stateChangesLabel: formatRecord(recordField(frame, "stateChanges") ?? {}, "not captured in this recording"),
|
||||||
|
}];
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user