fix: expose live run action in agent handoff scene
This commit is contained in:
@@ -214,7 +214,13 @@ describe("PresentationRoute", () => {
|
|||||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
|
|
||||||
expect(await screen.findByRole("button", { name: /run prepared workflow|run replay walkthrough/i })).toBeInTheDocument();
|
const actions = await screen.findAllByRole("button", {
|
||||||
|
name: /run prepared workflow|run replay walkthrough/i,
|
||||||
|
});
|
||||||
|
expect(actions.length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(
|
||||||
|
document.querySelector(".agent-handoff-scene .assistant-operator-thread__action button"),
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses stored target for live presentation mode", async () => {
|
it("uses stored target for live presentation mode", async () => {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export const PresentationStage = ({
|
|||||||
<SceneBody
|
<SceneBody
|
||||||
location={state.location}
|
location={state.location}
|
||||||
demo={demo}
|
demo={demo}
|
||||||
|
timelineAgent={timelineAgent}
|
||||||
selectedNodeId={state.selectedNodeId}
|
selectedNodeId={state.selectedNodeId}
|
||||||
selectNode={selectNode}
|
selectNode={selectNode}
|
||||||
openEvidence={openEvidence}
|
openEvidence={openEvidence}
|
||||||
@@ -107,4 +108,4 @@ export const PresentationStage = ({
|
|||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
</LazyMotion>
|
</LazyMotion>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
|
import type { TimelineAgentController } from "../demo/agent/timelineAgent.js";
|
||||||
import type { DemoApprovalActions } from "./demo-approval-actions.js";
|
import type { DemoApprovalActions } from "./demo-approval-actions.js";
|
||||||
import { AgentHandoffScene } from "./authoring/AgentHandoffScene.js";
|
import { AgentHandoffScene } from "./authoring/AgentHandoffScene.js";
|
||||||
import {
|
import {
|
||||||
@@ -25,6 +26,7 @@ import { ProblemLoopScene } from "./opening/ProblemLoopScene.js";
|
|||||||
type SceneBodyProps = {
|
type SceneBodyProps = {
|
||||||
readonly location: PresentationLocation;
|
readonly location: PresentationLocation;
|
||||||
readonly demo: DemoTimelineController;
|
readonly demo: DemoTimelineController;
|
||||||
|
readonly timelineAgent?: TimelineAgentController | undefined;
|
||||||
readonly selectedNodeId: string | null;
|
readonly selectedNodeId: string | null;
|
||||||
readonly selectNode: (nodeId: string | null) => void;
|
readonly selectNode: (nodeId: string | null) => void;
|
||||||
readonly openEvidence: () => void;
|
readonly openEvidence: () => void;
|
||||||
@@ -303,7 +305,7 @@ const assertNever = (value: never): never => {
|
|||||||
throw new Error(`Unexpected view: ${value}`);
|
throw new Error(`Unexpected view: ${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions }: SceneBodyProps) => {
|
export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions }: SceneBodyProps) => {
|
||||||
const sceneId = location.kind === "main" ? location.sceneId : "positioning";
|
const sceneId = location.kind === "main" ? location.sceneId : "positioning";
|
||||||
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
||||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||||
@@ -339,7 +341,7 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
|||||||
case "authoring":
|
case "authoring":
|
||||||
return <AuthoringScene scene={scene} beat={beat} />;
|
return <AuthoringScene scene={scene} beat={beat} />;
|
||||||
case "agent":
|
case "agent":
|
||||||
return <AgentHandoffScene scene={scene} beat={beat} />;
|
return <AgentHandoffScene scene={scene} beat={beat} timelineAgent={timelineAgent} />;
|
||||||
case "demo-lifecycle":
|
case "demo-lifecycle":
|
||||||
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} />;
|
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} />;
|
||||||
case "demo":
|
case "demo":
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cleanup, render, screen } from "@testing-library/react";
|
import { cleanup, render, screen } from "@testing-library/react";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { TimelineAgentController } from "../../demo/agent/timelineAgent.js";
|
||||||
import { findBeat, findScene } from "../storyboard.js";
|
import { findBeat, findScene } from "../storyboard.js";
|
||||||
import { AgentHandoffScene } from "./AgentHandoffScene.js";
|
import { AgentHandoffScene } from "./AgentHandoffScene.js";
|
||||||
|
|
||||||
@@ -68,4 +69,29 @@ describe("AgentHandoffScene", () => {
|
|||||||
renderBeat("handoff");
|
renderBeat("handoff");
|
||||||
expect(screen.queryByText("prepared workflow lifecycle")).not.toBeInTheDocument();
|
expect(screen.queryByText("prepared workflow lifecycle")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exposes the live prepared-workflow action inside the visible conversation", () => {
|
||||||
|
const runPreparedWorkflow = vi.fn();
|
||||||
|
const timelineAgent = {
|
||||||
|
messages: [],
|
||||||
|
canRun: true,
|
||||||
|
runLabel: "Run prepared workflow",
|
||||||
|
runPreparedWorkflow,
|
||||||
|
submitSelectedIssues: vi.fn(async () => {}),
|
||||||
|
cancelReview: vi.fn(async () => {}),
|
||||||
|
} as unknown as TimelineAgentController;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AgentHandoffScene
|
||||||
|
scene={findScene("agent-handoff")!}
|
||||||
|
beat={findBeat("agent-handoff", "request")!}
|
||||||
|
timelineAgent={timelineAgent}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const action = screen.getByRole("button", { name: "Run prepared workflow" });
|
||||||
|
expect(action).toBeEnabled();
|
||||||
|
action.click();
|
||||||
|
expect(runPreparedWorkflow).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { AuthoringConversation } from "./AuthoringConversation.js";
|
import { AuthoringConversation } from "./AuthoringConversation.js";
|
||||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||||
|
import type { TimelineAgentController } from "../../demo/agent/timelineAgent.js";
|
||||||
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
|
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
|
||||||
|
|
||||||
type AgentHandoffSceneProps = {
|
type AgentHandoffSceneProps = {
|
||||||
readonly scene: SceneDefinition;
|
readonly scene: SceneDefinition;
|
||||||
readonly beat: SceneBeatDefinition;
|
readonly beat: SceneBeatDefinition;
|
||||||
|
readonly timelineAgent?: TimelineAgentController | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,10 +14,10 @@ type AgentHandoffSceneProps = {
|
|||||||
*
|
*
|
||||||
* The request beat shows the operator asking the agent to prepare a report;
|
* The request beat shows the operator asking the agent to prepare a report;
|
||||||
* the handoff beat reveals the full completed conversation with all phases.
|
* the handoff beat reveals the full completed conversation with all phases.
|
||||||
* Neither run actions nor approval actions are passed because this is a
|
* The conversation is a prepared recording, but the shared run action can
|
||||||
* prepared recording, not a live agent interaction.
|
* start the configured live workflow when a target is available.
|
||||||
*/
|
*/
|
||||||
export const AgentHandoffScene = ({ beat }: AgentHandoffSceneProps) => {
|
export const AgentHandoffScene = ({ beat, timelineAgent }: AgentHandoffSceneProps) => {
|
||||||
const phase: AuthoringPhaseId = beat.id === "handoff" ? "deployment" : "discover";
|
const phase: AuthoringPhaseId = beat.id === "handoff" ? "deployment" : "discover";
|
||||||
const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }[] = [
|
const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }[] = [
|
||||||
{ id: "discover", label: "Discover" },
|
{ id: "discover", label: "Discover" },
|
||||||
@@ -46,6 +48,11 @@ export const AgentHandoffScene = ({ beat }: AgentHandoffSceneProps) => {
|
|||||||
throughPhase={phase}
|
throughPhase={phase}
|
||||||
activePhase={phase}
|
activePhase={phase}
|
||||||
surface="stage"
|
surface="stage"
|
||||||
|
runAction={timelineAgent ? {
|
||||||
|
label: timelineAgent.runLabel,
|
||||||
|
disabled: !timelineAgent.canRun,
|
||||||
|
run: () => { void timelineAgent.runPreparedWorkflow(); },
|
||||||
|
} : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type AuthoringConversationProps = {
|
|||||||
readonly throughPhase: AuthoringPhaseId;
|
readonly throughPhase: AuthoringPhaseId;
|
||||||
readonly activePhase: AuthoringPhaseId;
|
readonly activePhase: AuthoringPhaseId;
|
||||||
readonly surface: "stage" | "dock";
|
readonly surface: "stage" | "dock";
|
||||||
|
readonly runAction?: { readonly label: string; readonly disabled: boolean; readonly run: () => void } | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Renders the same prepared conversation at full-stage or compact-dock scale. */
|
/** Renders the same prepared conversation at full-stage or compact-dock scale. */
|
||||||
@@ -16,6 +17,7 @@ export const AuthoringConversation = ({
|
|||||||
throughPhase,
|
throughPhase,
|
||||||
activePhase,
|
activePhase,
|
||||||
surface,
|
surface,
|
||||||
|
runAction,
|
||||||
}: AuthoringConversationProps) => (
|
}: AuthoringConversationProps) => (
|
||||||
<AssistantOperatorThread
|
<AssistantOperatorThread
|
||||||
mode={surface === "stage" ? "full" : "dock"}
|
mode={surface === "stage" ? "full" : "dock"}
|
||||||
@@ -23,5 +25,6 @@ export const AuthoringConversation = ({
|
|||||||
messages={projectPreparedAuthoringThread(throughPhase)}
|
messages={projectPreparedAuthoringThread(throughPhase)}
|
||||||
activeToolGroupId={authoringToolGroupId(activePhase)}
|
activeToolGroupId={authoringToolGroupId(activePhase)}
|
||||||
ariaLabel="prepared authoring conversation"
|
ariaLabel="prepared authoring conversation"
|
||||||
|
runAction={runAction}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user