refactor: name presentation evidence states

This commit is contained in:
lda
2026-07-06 03:56:53 +07:00 Verified
parent bd2081fe8a
commit baf26156e5
14 changed files with 108 additions and 89 deletions
@@ -5,7 +5,6 @@ export type AgentRole = "user" | "assistant";
export type PresentationToolAction = export type PresentationToolAction =
| { readonly type: "selectWorkflowNode"; readonly nodeId: string } | { readonly type: "selectWorkflowNode"; readonly nodeId: string }
| { readonly type: "focusOperation"; readonly eventId: string } | { readonly type: "focusOperation"; readonly eventId: string }
| { readonly type: "openEvidence"; readonly eventId: string }
| { readonly type: "showTraceFrame"; readonly frameIndex: number }; | { readonly type: "showTraceFrame"; readonly frameIndex: number };
export type AgentToolCall = { export type AgentToolCall = {
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { loadCanonicalDemoRecording } from "../timeline/replay.js"; import { loadCanonicalDemoRecording } from "../timeline/replay.js";
import { runPreparedRecipeReplay } from "./preparedRecipeDriver.js"; import { runPreparedRecipeReplay } from "./preparedRecipeDriver.js";
import { isAllowedAgentToolName } from "./tools.js";
const collect = async <T>(events: AsyncIterable<T>): Promise<ReadonlyArray<T>> => { const collect = async <T>(events: AsyncIterable<T>): Promise<ReadonlyArray<T>> => {
const collected: T[] = []; const collected: T[] = [];
@@ -38,8 +39,9 @@ describe("prepared recipe driver", () => {
"selectWorkflowNode", "selectWorkflowNode",
"resumeIssueReview", "resumeIssueReview",
"readRunTrace", "readRunTrace",
"openEvidence",
]); ]);
expect(isAllowedAgentToolName("openEvidence")).toBe(false);
}); });
it("emits approval-request at resumeIssueReview and waits for decision", async () => { it("emits approval-request at resumeIssueReview and waits for decision", async () => {
@@ -112,17 +112,6 @@ export async function* runPreparedRecipeReplay(
eventId: trace?.id ?? null, eventId: trace?.id ?? null,
}); });
break; break;
case "openEvidence":
yield {
id: step.id,
role: "assistant",
parts: [
agentToolCallPart(`${step.id}-call`, step.toolName, { eventId: trace?.id ?? "trace" }),
presentationActionPart({ type: "openEvidence", eventId: trace?.id ?? "trace" }),
agentToolResultPart(`${step.id}-call`, step.toolName, "success", { eventId: trace?.id ?? "trace" }),
],
};
break;
default: default:
assertNever(step.toolName); assertNever(step.toolName);
} }
+1 -7
View File
@@ -5,8 +5,7 @@ export type RecipeTool =
| "startPreparedReportRun" | "startPreparedReportRun"
| "selectWorkflowNode" | "selectWorkflowNode"
| "resumeIssueReview" | "resumeIssueReview"
| "readRunTrace" | "readRunTrace";
| "openEvidence";
export type PreparedRecipeStep = { export type PreparedRecipeStep = {
readonly id: string; readonly id: string;
@@ -58,10 +57,5 @@ export const PREPARE_THESIS_REPORT_RECIPE: PreparedRecipe = {
narration: "I will read the run trace as evidence.", narration: "I will read the run trace as evidence.",
toolName: "readRunTrace", toolName: "readRunTrace",
}, },
{
id: "open-evidence",
narration: "I will open the evidence linked to the trace call.",
toolName: "openEvidence",
},
], ],
}; };
@@ -8,7 +8,6 @@ describe("agent tools", () => {
expect(AGENT_TOOLS.resumeIssueReview.kind).toBe("workflow"); expect(AGENT_TOOLS.resumeIssueReview.kind).toBe("workflow");
expect(AGENT_TOOLS.readRunTrace.kind).toBe("workflow"); expect(AGENT_TOOLS.readRunTrace.kind).toBe("workflow");
expect(AGENT_TOOLS.selectWorkflowNode.kind).toBe("presentation"); expect(AGENT_TOOLS.selectWorkflowNode.kind).toBe("presentation");
expect(AGENT_TOOLS.openEvidence.kind).toBe("presentation");
}); });
it("rejects unknown tool names", () => { it("rejects unknown tool names", () => {
-6
View File
@@ -7,7 +7,6 @@ export type WorkflowToolName =
export type PresentationToolName = export type PresentationToolName =
| "selectWorkflowNode" | "selectWorkflowNode"
| "focusOperation" | "focusOperation"
| "openEvidence"
| "showTraceFrame"; | "showTraceFrame";
export type AgentToolName = WorkflowToolName | PresentationToolName; export type AgentToolName = WorkflowToolName | PresentationToolName;
@@ -49,11 +48,6 @@ export const AGENT_TOOLS = {
kind: "presentation", kind: "presentation",
description: "Focus an operation event in the presentation.", description: "Focus an operation event in the presentation.",
}, },
openEvidence: {
name: "openEvidence",
kind: "presentation",
description: "Open evidence for an operation event.",
},
showTraceFrame: { showTraceFrame: {
name: "showTraceFrame", name: "showTraceFrame",
kind: "presentation", kind: "presentation",
@@ -18,7 +18,7 @@ const record: EvidenceRecord = {
describe("EvidenceDrawer", () => { describe("EvidenceDrawer", () => {
it("renders current evidence records and can close", () => { it("renders current evidence records and can close", () => {
const close = vi.fn(); const close = vi.fn();
render(<EvidenceDrawer records={[record]} mode="open" close={close} />); render(<EvidenceDrawer records={[record]} mode="receipt" close={close} />);
expect(screen.getByRole("complementary", { name: /presentation evidence/i })).toBeInTheDocument(); expect(screen.getByRole("complementary", { name: /presentation evidence/i })).toBeInTheDocument();
expect(screen.getByText(/workflow.runs.start/i)).toBeInTheDocument(); expect(screen.getByText(/workflow.runs.start/i)).toBeInTheDocument();
@@ -1,10 +1,10 @@
import type { EvidenceRecord } from "../app/state.js"; import type { EvidenceRecord } from "../app/state.js";
import { formatJson } from "./format.js"; import { formatJson } from "./format.js";
import type { EvidenceMode } from "./storyboard.js"; import type { EvidencePresentation } from "./storyboard.js";
type EvidenceDrawerProps = { type EvidenceDrawerProps = {
readonly records: readonly EvidenceRecord[]; readonly records: readonly EvidenceRecord[];
readonly mode: EvidenceMode; readonly mode: EvidencePresentation;
readonly close: () => void; readonly close: () => void;
}; };
@@ -117,14 +117,6 @@ export const PresentationRoute = () => {
case "selectWorkflowNode": case "selectWorkflowNode":
dispatch({ type: "select_node", nodeId: action.nodeId }); dispatch({ type: "select_node", nodeId: action.nodeId });
break; break;
case "openEvidence": {
const hasLiveEvidence = evidence.length > 0;
if (!hasLiveEvidence) {
setEvidence(replayEvidence);
}
dispatch({ type: "set_evidence_mode", mode: "open" });
break;
}
case "focusOperation": case "focusOperation":
case "showTraceFrame": case "showTraceFrame":
break; break;
@@ -164,7 +156,7 @@ export const PresentationRoute = () => {
onDeny={agent.phase === "awaiting-approval" ? handleDeny : undefined} onDeny={agent.phase === "awaiting-approval" ? handleDeny : undefined}
jump={handleJump} jump={handleJump}
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })} selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
openEvidence={() => dispatch({ type: "set_evidence_mode", mode: "open" })} openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}
closeOverlay={() => dispatch({ type: "close_overlay" })} closeOverlay={() => dispatch({ type: "close_overlay" })}
openDiscussion={handleOpenDiscussion} openDiscussion={handleOpenDiscussion}
closeDiscussion={handleCloseDiscussion} closeDiscussion={handleCloseDiscussion}
@@ -53,7 +53,7 @@ export const PresentationStage = ({
<div <div
className="presentation-stage" className="presentation-stage"
data-chat-mode={composition.chatMode} data-chat-mode={composition.chatMode}
data-evidence-mode={composition.evidenceMode} data-evidence-presentation={composition.evidencePresentation}
data-scene-view={activeSceneView} data-scene-view={activeSceneView}
> >
<aside className="presentation-stage__chat" aria-label="agent chat region"> <aside className="presentation-stage__chat" aria-label="agent chat region">
@@ -79,7 +79,7 @@ export const PresentationStage = ({
)} )}
</section> </section>
<aside className="presentation-stage__evidence" aria-label="evidence region"> <aside className="presentation-stage__evidence" aria-label="evidence region">
<EvidenceDrawer records={evidence} mode={composition.evidenceMode} close={closeOverlay} /> <EvidenceDrawer records={evidence} mode={composition.evidencePresentation} close={closeOverlay} />
</aside> </aside>
{state.location.kind === "main" && ( {state.location.kind === "main" && (
<SceneProgress location={state.location} /> <SceneProgress location={state.location} />
@@ -72,27 +72,27 @@ describe("presentationReducer", () => {
expect(state.selectedNodeId).toBe("review_issues"); expect(state.selectedNodeId).toBe("review_issues");
}); });
it("closes overlays in priority order: node, evidence, discussion", () => { it("closes overlays in priority order: inspector, node, discussion", () => {
const withNode = presentationReducer(initialPresentationState, { const withNode = presentationReducer(initialPresentationState, {
type: "select_node", type: "select_node",
nodeId: "review_issues", nodeId: "review_issues",
}); });
const withEvidence = presentationReducer(withNode, { const withInspector = presentationReducer(withNode, {
type: "set_evidence_mode", type: "set_evidence_presentation",
mode: "open", presentation: "inspector",
}); });
const opened = presentationReducer(withEvidence, { const opened = presentationReducer(withInspector, {
type: "open_discussion", type: "open_discussion",
branchId: "hosted-automation", branchId: "hosted-automation",
}); });
const closed1 = presentationReducer(opened, { type: "close_overlay" }); const closed1 = presentationReducer(opened, { type: "close_overlay" });
expect(closed1.selectedNodeId).toBeNull(); expect(closed1.evidencePresentationOverride).toBe("hidden");
expect(closed1.evidenceModeOverride).toBe("open"); expect(closed1.selectedNodeId).toBe("review_issues");
expect(closed1.location.kind).toBe("discussion"); expect(closed1.location.kind).toBe("discussion");
const closed2 = presentationReducer(closed1, { type: "close_overlay" }); const closed2 = presentationReducer(closed1, { type: "close_overlay" });
expect(closed2.evidenceModeOverride).toBe("hidden"); expect(closed2.selectedNodeId).toBeNull();
const closed3 = presentationReducer(closed2, { type: "close_overlay" }); const closed3 = presentationReducer(closed2, { type: "close_overlay" });
expect(closed3.location.kind).toBe("main"); expect(closed3.location.kind).toBe("main");
@@ -123,11 +123,11 @@ describe("presentationReducer", () => {
it("closes overlays before rewinding content", () => { it("closes overlays before rewinding content", () => {
const opened = presentationReducer(initialPresentationState, { const opened = presentationReducer(initialPresentationState, {
type: "set_evidence_mode", type: "set_evidence_presentation",
mode: "open", presentation: "inspector",
}); });
const closed = presentationReducer(opened, { type: "close_overlay" }); const closed = presentationReducer(opened, { type: "close_overlay" });
expect(closed.evidenceModeOverride).toBe("hidden"); expect(closed.evidencePresentationOverride).toBe("hidden");
expect(closed.location).toEqual(initialPresentationState.location); expect(closed.location).toEqual(initialPresentationState.location);
}); });
@@ -136,13 +136,14 @@ describe("presentationReducer", () => {
type: "jump", type: "jump",
location: { kind: "main", sceneId: "interrupt-evidence", beatId: "trace", focusPath: [] }, location: { kind: "main", sceneId: "interrupt-evidence", beatId: "trace", focusPath: [] },
}); });
expect(stateAtTrace.evidenceModeOverride).toBeNull(); expect(stateAtTrace.evidencePresentationOverride).toBeNull();
const closed = presentationReducer(stateAtTrace, { type: "close_overlay" }); const closed = presentationReducer(stateAtTrace, { type: "close_overlay" });
expect(closed.evidenceModeOverride).toBe("hidden"); expect(closed.evidencePresentationOverride).toBeNull();
expect(closed.location.kind).toBe("main");
const secondEscape = presentationReducer(closed, { type: "close_overlay" }); const secondEscape = presentationReducer(closed, { type: "close_overlay" });
expect(secondEscape.evidenceModeOverride).toBe("hidden"); expect(secondEscape.evidencePresentationOverride).toBeNull();
expect(secondEscape.location.kind).toBe("main"); expect(secondEscape.location.kind).toBe("main");
}); });
@@ -183,4 +184,51 @@ describe("presentationReducer", () => {
expect(presentationReducer(opened, { type: "close_discussion" }).location) expect(presentationReducer(opened, { type: "close_discussion" }).location)
.toEqual(deepRuntimeState.location); .toEqual(deepRuntimeState.location);
}); });
it("derives a receipt from beat metadata without opening an inspector", () => {
const state = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "architecture", beatId: "node-use", focusPath: ["node-use"] },
});
expect(compositionForState(state).evidencePresentation).toBe("receipt");
expect(state.evidencePresentationOverride).toBeNull();
});
it("closes an explicit inspector before the node spotlight", () => {
const withNode = presentationReducer(initialPresentationState, {
type: "select_node",
nodeId: "review_issues",
});
const withInspector = presentationReducer(withNode, {
type: "set_evidence_presentation",
presentation: "inspector",
});
const firstEscape = presentationReducer(withInspector, { type: "close_overlay" });
expect(firstEscape.evidencePresentationOverride).toBe("hidden");
expect(firstEscape.selectedNodeId).toBe("review_issues");
const secondEscape = presentationReducer(firstEscape, { type: "close_overlay" });
expect(secondEscape.selectedNodeId).toBeNull();
});
it("closes the inspector and recomputes receipt state when the beat changes", () => {
const atReceiptBeat = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "architecture", beatId: "node-use", focusPath: ["node-use"] },
});
const opened = presentationReducer(atReceiptBeat, {
type: "set_evidence_presentation",
presentation: "inspector",
});
const advanced = presentationReducer(opened, { type: "next" });
expect(advanced.evidencePresentationOverride).toBeNull();
expect(compositionForState(advanced).evidencePresentation).not.toBe("inspector");
});
it("does not treat a receipt as an Escape-closeable overlay", () => {
const receipt = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "authoring", beatId: "diagnose", focusPath: [] },
});
expect(presentationReducer(receipt, { type: "close_overlay" })).toEqual(receipt);
});
}); });
@@ -2,9 +2,10 @@ import {
defaultMainLocation, defaultMainLocation,
findDiscussionBranch, findDiscussionBranch,
findScene, findScene,
type BeatEvidencePresentation,
type ChatMode, type ChatMode,
type DiscussionBranchId, type DiscussionBranchId,
type EvidenceMode, type EvidencePresentation,
type MainLocation, type MainLocation,
type PresentationLocation, type PresentationLocation,
} from "./storyboard.js"; } from "./storyboard.js";
@@ -19,7 +20,7 @@ export type PresentationState = {
readonly location: PresentationLocation; readonly location: PresentationLocation;
readonly discussionReturn: MainLocation | null; readonly discussionReturn: MainLocation | null;
readonly selectedNodeId: string | null; readonly selectedNodeId: string | null;
readonly evidenceModeOverride: EvidenceMode | null; readonly evidencePresentationOverride: EvidencePresentation | null;
readonly playbackMode: "replay" | "live"; readonly playbackMode: "replay" | "live";
readonly motionDisabled: boolean; readonly motionDisabled: boolean;
readonly startedAt: number; readonly startedAt: number;
@@ -34,7 +35,7 @@ export type PresentationAction =
| { readonly type: "close_discussion" } | { readonly type: "close_discussion" }
| { readonly type: "select_node"; readonly nodeId: string } | { readonly type: "select_node"; readonly nodeId: string }
| { readonly type: "clear_node" } | { readonly type: "clear_node" }
| { readonly type: "set_evidence_mode"; readonly mode: EvidenceMode } | { readonly type: "set_evidence_presentation"; readonly presentation: EvidencePresentation }
| { readonly type: "close_overlay" } | { readonly type: "close_overlay" }
| { readonly type: "set_playback_mode"; readonly mode: PresentationState["playbackMode"] } | { readonly type: "set_playback_mode"; readonly mode: PresentationState["playbackMode"] }
| { readonly type: "set_focus_path"; readonly path: readonly string[] } | { readonly type: "set_focus_path"; readonly path: readonly string[] }
@@ -44,7 +45,7 @@ export const initialPresentationState: PresentationState = {
location: defaultMainLocation, location: defaultMainLocation,
discussionReturn: null, discussionReturn: null,
selectedNodeId: null, selectedNodeId: null,
evidenceModeOverride: null, evidencePresentationOverride: null,
playbackMode: "replay", playbackMode: "replay",
motionDisabled: false, motionDisabled: false,
startedAt: Date.now(), startedAt: Date.now(),
@@ -52,29 +53,29 @@ export const initialPresentationState: PresentationState = {
const compositionForLocation = ( const compositionForLocation = (
location: PresentationLocation, location: PresentationLocation,
evidenceOverride: EvidenceMode | null, evidenceOverride: EvidencePresentation | null,
): { ): {
readonly chatMode: ChatMode; readonly chatMode: ChatMode;
readonly evidenceMode: EvidenceMode; readonly evidencePresentation: EvidencePresentation;
} => { } => {
if (location.kind === "discussion") { if (location.kind === "discussion") {
return { return {
chatMode: "hidden", chatMode: "hidden",
evidenceMode: evidenceOverride ?? "hidden", evidencePresentation: evidenceOverride ?? "hidden",
}; };
} }
const scene = findScene(location.sceneId); const scene = findScene(location.sceneId);
const beat = scene?.beats.find((b) => b.id === location.beatId); const beat = scene?.beats.find((b) => b.id === location.beatId);
return { return {
chatMode: beat?.chatMode ?? "hidden", chatMode: beat?.chatMode ?? "hidden",
evidenceMode: evidenceOverride ?? beat?.evidenceMode ?? "hidden", evidencePresentation: evidenceOverride ?? beat?.evidencePresentation ?? "hidden",
}; };
}; };
export const compositionForState = (state: PresentationState) => export const compositionForState = (state: PresentationState) =>
compositionForLocation( compositionForLocation(
state.location, state.location,
state.evidenceModeOverride, state.evidencePresentationOverride,
); );
const isValidMainLocation = (location: PresentationLocation): location is MainLocation => const isValidMainLocation = (location: PresentationLocation): location is MainLocation =>
@@ -93,6 +94,14 @@ const clampMainLocation = (location: MainLocation): MainLocation => {
return defaultMainLocation; return defaultMainLocation;
}; };
const moveToLocation = (
state: PresentationState,
location: PresentationLocation,
): PresentationState => ({
...state,
location,
});
export const presentationReducer = ( export const presentationReducer = (
state: PresentationState, state: PresentationState,
action: PresentationAction, action: PresentationAction,
@@ -101,27 +110,27 @@ export const presentationReducer = (
case "next": { case "next": {
if (!isValidMainLocation(state.location)) return state; if (!isValidMainLocation(state.location)) return state;
const next = nextMainLocation(state.location); const next = nextMainLocation(state.location);
return { ...state, location: next }; return { ...moveToLocation(state, next), evidencePresentationOverride: null };
} }
case "previous": { case "previous": {
if (!isValidMainLocation(state.location)) return state; if (!isValidMainLocation(state.location)) return state;
const prev = previousMainLocation(state.location); const prev = previousMainLocation(state.location);
return { ...state, location: prev }; return { ...moveToLocation(state, prev), evidencePresentationOverride: null };
} }
case "jump": { case "jump": {
if (state.location.kind === "discussion") return state; if (state.location.kind === "discussion") return state;
return { ...state, location: action.location }; return { ...moveToLocation(state, action.location), evidencePresentationOverride: null };
} }
case "jump_hash": { case "jump_hash": {
const parsed = locationFromHash(action.hash); const parsed = locationFromHash(action.hash);
if (parsed.kind === "main") { if (parsed.kind === "main") {
return { ...state, location: clampMainLocation(parsed), discussionReturn: null }; return { ...moveToLocation(state, clampMainLocation(parsed)), discussionReturn: null, evidencePresentationOverride: null };
} }
const branch = findDiscussionBranch(parsed.branchId); const branch = findDiscussionBranch(parsed.branchId);
const returnLoc = branch const returnLoc = branch
? firstBeatOfScene(branch.parentSceneId) ?? defaultMainLocation ? firstBeatOfScene(branch.parentSceneId) ?? defaultMainLocation
: defaultMainLocation; : defaultMainLocation;
return { ...state, location: parsed, discussionReturn: returnLoc }; return { ...moveToLocation(state, parsed), discussionReturn: returnLoc };
} }
case "open_discussion": { case "open_discussion": {
const branch = findDiscussionBranch(action.branchId); const branch = findDiscussionBranch(action.branchId);
@@ -138,8 +147,7 @@ export const presentationReducer = (
case "close_discussion": { case "close_discussion": {
if (state.location.kind !== "discussion") return state; if (state.location.kind !== "discussion") return state;
return { return {
...state, ...moveToLocation(state, state.discussionReturn ?? defaultMainLocation),
location: state.discussionReturn ?? defaultMainLocation,
discussionReturn: null, discussionReturn: null,
}; };
} }
@@ -147,18 +155,11 @@ export const presentationReducer = (
return { ...state, selectedNodeId: action.nodeId }; return { ...state, selectedNodeId: action.nodeId };
case "clear_node": case "clear_node":
return { ...state, selectedNodeId: null }; return { ...state, selectedNodeId: null };
case "set_evidence_mode": case "set_evidence_presentation":
return { ...state, evidenceModeOverride: action.mode }; return { ...state, evidencePresentationOverride: action.presentation };
case "close_overlay": { case "close_overlay": {
if (state.evidencePresentationOverride === "inspector") return { ...state, evidencePresentationOverride: "hidden" };
if (state.selectedNodeId !== null) return { ...state, selectedNodeId: null }; if (state.selectedNodeId !== null) return { ...state, selectedNodeId: null };
const isEvidenceVisible = (() => {
if (state.evidenceModeOverride !== null) return state.evidenceModeOverride !== "hidden";
if (state.location.kind === "discussion") return false;
const scene = findScene(state.location.sceneId);
const beat = scene?.beats.find((b) => b.id === (state.location as MainLocation).beatId);
return beat?.evidenceMode === "open" || beat?.evidenceMode === "peek";
})();
if (isEvidenceVisible) return { ...state, evidenceModeOverride: "hidden" };
if (state.location.kind === "discussion") { if (state.location.kind === "discussion") {
return { return {
...state, ...state,
@@ -48,7 +48,7 @@ const mockBeat = {
caption: "Human and agent clients use the same public lifecycle surface.", caption: "Human and agent clients use the same public lifecycle surface.",
chatMode: "rail" as const, chatMode: "rail" as const,
chatTheme: "light" as const, chatTheme: "light" as const,
evidenceMode: "hidden" as const, evidencePresentation: "hidden" as const,
figure: { catalogId: "system-architecture", focusPath: [] as readonly string[], activeNodeId: "client-operations" }, figure: { catalogId: "system-architecture", focusPath: [] as readonly string[], activeNodeId: "client-operations" },
}; };
@@ -2,7 +2,8 @@ export type ClaimClass = "motivation" | "implemented" | "evaluated" | "external-
export type StageTheme = "paper" | "night"; export type StageTheme = "paper" | "night";
export type ChatTheme = "light" | "dark"; export type ChatTheme = "light" | "dark";
export type ChatMode = "hidden" | "full" | "rail" | "dock"; export type ChatMode = "hidden" | "full" | "rail" | "dock";
export type EvidenceMode = "hidden" | "peek" | "open"; export type EvidencePresentation = "hidden" | "receipt" | "inspector";
export type BeatEvidencePresentation = Exclude<EvidencePresentation, "inspector">;
export type SceneView = export type SceneView =
| "narrative" | "narrative"
| "positioning" | "positioning"
@@ -27,7 +28,7 @@ export type SceneBeatDefinition = {
readonly caption: string; readonly caption: string;
readonly chatMode: ChatMode; readonly chatMode: ChatMode;
readonly chatTheme: ChatTheme; readonly chatTheme: ChatTheme;
readonly evidenceMode: EvidenceMode; readonly evidencePresentation: BeatEvidencePresentation;
readonly figure: FigureBeatDefinition | null; readonly figure: FigureBeatDefinition | null;
}; };
@@ -48,14 +49,14 @@ const sceneBeat = (
id: string, id: string,
title: string, title: string,
caption: string, caption: string,
options: Partial<Pick<SceneBeatDefinition, "chatMode" | "chatTheme" | "evidenceMode" | "figure">> = {}, options: Partial<Pick<SceneBeatDefinition, "chatMode" | "chatTheme" | "evidencePresentation" | "figure">> = {},
): SceneBeatDefinition => ({ ): SceneBeatDefinition => ({
id, id,
title, title,
caption, caption,
chatMode: options.chatMode ?? "hidden", chatMode: options.chatMode ?? "hidden",
chatTheme: options.chatTheme ?? "dark", chatTheme: options.chatTheme ?? "dark",
evidenceMode: options.evidenceMode ?? "hidden", evidencePresentation: options.evidencePresentation ?? "hidden",
figure: options.figure ?? null, figure: options.figure ?? null,
}); });
@@ -140,7 +141,7 @@ export const mainScenes = defineScenes([
sceneBeat("client", "Client operations", "Human and agent clients use the same public lifecycle surface.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "client-operations" } }), sceneBeat("client", "Client operations", "Human and agent clients use the same public lifecycle surface.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "client-operations" } }),
sceneBeat("api", "Transport and API", "JSON-RPC reaches WorkflowApi without owning domain behavior.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "application-lifecycle" } }), sceneBeat("api", "Transport and API", "JSON-RPC reaches WorkflowApi without owning domain behavior.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "application-lifecycle" } }),
sceneBeat("runtime", "Runtime and providers", "The runtime resolves provider-neutral capabilities and stores lifecycle records.", { figure: { catalogId: "system-architecture", focusPath: ["runtime-providers"], activeNodeId: "configured-providers" } }), sceneBeat("runtime", "Runtime and providers", "The runtime resolves provider-neutral capabilities and stores lifecycle records.", { figure: { catalogId: "system-architecture", focusPath: ["runtime-providers"], activeNodeId: "configured-providers" } }),
sceneBeat("node-use", "NodeUse", "One callable node validates input, invokes a capability, and reduces output into state.", { evidenceMode: "peek", figure: { catalogId: "system-architecture", focusPath: ["node-use"], activeNodeId: "invoke-handler" } }), sceneBeat("node-use", "NodeUse", "One callable node validates input, invokes a capability, and reduces output into state.", { evidencePresentation: "receipt", figure: { catalogId: "system-architecture", focusPath: ["node-use"], activeNodeId: "invoke-handler" } }),
], ],
}, },
{ {
@@ -154,7 +155,7 @@ export const mainScenes = defineScenes([
beats: [ beats: [
sceneBeat("discover", "Discover", "Inspect capabilities and schemas before authoring."), sceneBeat("discover", "Discover", "Inspect capabilities and schemas before authoring."),
sceneBeat("author", "Author", "Focused operations build and connect the draft."), sceneBeat("author", "Author", "Focused operations build and connect the draft."),
sceneBeat("diagnose", "Diagnose", "Structured diagnostics identify invalid state.", { evidenceMode: "peek" }), sceneBeat("diagnose", "Diagnose", "Structured diagnostics identify invalid state.", { evidencePresentation: "receipt" }),
sceneBeat("repair", "Repair", "Repair hints lead to a valid compiled workflow."), sceneBeat("repair", "Repair", "Repair hints lead to a valid compiled workflow."),
], ],
}, },
@@ -197,7 +198,7 @@ export const mainScenes = defineScenes([
sceneBeat("approval", "Approval", "The operator reviews a schema-backed resume request.", { chatMode: "rail", chatTheme: "light" }), sceneBeat("approval", "Approval", "The operator reviews a schema-backed resume request.", { chatMode: "rail", chatTheme: "light" }),
sceneBeat("resume", "Resume", "The approved payload resumes the same persisted run.", { chatMode: "rail", chatTheme: "light" }), sceneBeat("resume", "Resume", "The approved payload resumes the same persisted run.", { chatMode: "rail", chatTheme: "light" }),
sceneBeat("output", "Output", "The workflow produces the report and issue-board changes.", { chatMode: "dock", chatTheme: "light" }), sceneBeat("output", "Output", "The workflow produces the report and issue-board changes.", { chatMode: "dock", chatTheme: "light" }),
sceneBeat("trace", "Evidence", "Trace frames and protocol evidence remain inspectable.", { chatMode: "dock", chatTheme: "light", evidenceMode: "open" }), sceneBeat("trace", "Evidence", "Trace frames and protocol evidence remain inspectable.", { chatMode: "dock", chatTheme: "light", evidencePresentation: "receipt" }),
], ],
}, },
{ {