feat: give the agent handoff and demo one visual spine
This commit is contained in:
@@ -10,28 +10,48 @@ const renderBeat = (beatId: "request" | "handoff") => {
|
||||
return render(<AgentHandoffScene scene={scene} beat={beat} />);
|
||||
};
|
||||
|
||||
describe("AgentHandoffScene", () => {
|
||||
afterEach(cleanup);
|
||||
afterEach(cleanup);
|
||||
|
||||
it("renders a log region named prepared authoring conversation", () => {
|
||||
renderBeat("request");
|
||||
expect(screen.getByRole("log", { name: "prepared authoring conversation" })).toBeInTheDocument();
|
||||
describe("AgentHandoffScene", () => {
|
||||
it("renders one prepared conversation with a phase orientation rail", () => {
|
||||
render(
|
||||
<AgentHandoffScene
|
||||
scene={findScene("agent-handoff")!}
|
||||
beat={findBeat("agent-handoff", "request")!}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("region", { name: "prepared agent handoff" })).toHaveAttribute(
|
||||
"data-handoff-phase",
|
||||
"discover",
|
||||
);
|
||||
expect(screen.getByRole("log", { name: "prepared authoring conversation" })).toHaveAttribute(
|
||||
"data-surface",
|
||||
"stage",
|
||||
);
|
||||
expect(screen.getByRole("list", { name: "prepared handoff phases" })).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/workflow\.sources\.list/i).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("advances the same conversation to deployment evidence", () => {
|
||||
render(
|
||||
<AgentHandoffScene
|
||||
scene={findScene("agent-handoff")!}
|
||||
beat={findBeat("agent-handoff", "handoff")!}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("region", { name: "prepared agent handoff" })).toHaveAttribute(
|
||||
"data-handoff-phase",
|
||||
"deployment",
|
||||
);
|
||||
expect(screen.getAllByText(/workflow\.deployments\.save/i).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("renders separated user and assistant turns on the request beat", () => {
|
||||
renderBeat("request");
|
||||
const userMessages = screen.getAllByText(/report|workflow|prepare/i);
|
||||
expect(userMessages.length).toBeGreaterThanOrEqual(1);
|
||||
const assistantMessages = screen.getAllByText(/inspect|capabilities|sources|schemas|let me/i);
|
||||
expect(assistantMessages.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("renders the full conversation on the handoff beat", () => {
|
||||
renderBeat("handoff");
|
||||
const userMessages = screen.getAllByText(/report|workflow|prepare|save/i);
|
||||
expect(userMessages.length).toBeGreaterThanOrEqual(1);
|
||||
const assistantMessages = screen.getAllByText(/inspect|sources|capabilities|compile|deployment|artifact/i);
|
||||
expect(assistantMessages.length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getAllByText(/report|workflow|prepare/i).length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText(/inspect|capabilities|sources|schemas|let me/i).length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("interleaves prepared workflow tool groups with the handoff conversation", () => {
|
||||
@@ -44,9 +64,7 @@ describe("AgentHandoffScene", () => {
|
||||
it("does not render prepared workflow lifecycle content", () => {
|
||||
renderBeat("request");
|
||||
expect(screen.queryByText("prepared workflow lifecycle")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not include prepared workflow lifecycle content on handoff", () => {
|
||||
cleanup();
|
||||
renderBeat("handoff");
|
||||
expect(screen.queryByText("prepared workflow lifecycle")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthoringConversation } from "./AuthoringConversation.js";
|
||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
|
||||
|
||||
type AgentHandoffSceneProps = {
|
||||
@@ -15,13 +16,38 @@ type AgentHandoffSceneProps = {
|
||||
* prepared recording, not a live agent interaction.
|
||||
*/
|
||||
export const AgentHandoffScene = ({ beat }: AgentHandoffSceneProps) => {
|
||||
const phase = beat.id === "handoff" ? "deployment" : "discover";
|
||||
const phase: AuthoringPhaseId = beat.id === "handoff" ? "deployment" : "discover";
|
||||
const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }[] = [
|
||||
{ id: "discover", label: "Discover" },
|
||||
{ id: "draft", label: "Draft" },
|
||||
{ id: "validate", label: "Validate" },
|
||||
{ id: "artifact", label: "Artifact" },
|
||||
{ id: "deployment", label: "Deployment" },
|
||||
];
|
||||
const activeIndex = phases.findIndex(({ id }) => id === phase);
|
||||
|
||||
return (
|
||||
<AuthoringConversation
|
||||
throughPhase={phase}
|
||||
activePhase={phase}
|
||||
surface="stage"
|
||||
/>
|
||||
<section className="agent-handoff-scene" aria-label="prepared agent handoff" data-handoff-phase={phase}>
|
||||
<header className="agent-handoff-scene__header">
|
||||
<span>Prepared replay</span>
|
||||
<strong>External agent → workflow substrate</strong>
|
||||
<p>One conversation, with public operations and interpreted results.</p>
|
||||
</header>
|
||||
<ol className="agent-handoff-scene__rail" aria-label="prepared handoff phases">
|
||||
{phases.map((item, index) => (
|
||||
<li key={item.id} data-active={index === activeIndex ? "true" : "false"}>
|
||||
<span>{index + 1}</span>
|
||||
<strong>{item.label}</strong>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<div className="agent-handoff-scene__thread">
|
||||
<AuthoringConversation
|
||||
throughPhase={phase}
|
||||
activePhase={phase}
|
||||
surface="stage"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,6 +38,8 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
|
||||
className="prepared-lifecycle-scene"
|
||||
aria-label="prepared workflow authoring lifecycle"
|
||||
data-active-phase={beatId}
|
||||
data-primary-surface="authoring-phase"
|
||||
data-support-surface="prepared-chat"
|
||||
>
|
||||
<ol className="prepared-lifecycle-scene__rail" aria-label="authoring phase rail">
|
||||
{phases.map((phase) => (
|
||||
|
||||
Reference in New Issue
Block a user