fix: refresh timeline agent status intro

This commit is contained in:
lda
2026-07-09 16:30:57 +07:00 Verified
parent bf7db09467
commit cf59f16d46
3 changed files with 62 additions and 7 deletions
@@ -25,6 +25,13 @@ const failedStatus: PresentationTargetHealth = {
detail: "connection refused", detail: "connection refused",
}; };
const checkingStatus: PresentationTargetHealth = {
kind: "checking",
target: "http://127.0.0.1:8765/rpc",
label: "Live target configured",
detail: "checking",
};
const demoController = ( const demoController = (
overrides: Partial<DemoTimelineController> = {}, overrides: Partial<DemoTimelineController> = {},
): DemoTimelineController => ({ ): DemoTimelineController => ({
@@ -49,6 +56,34 @@ const demoController = (
}); });
describe("useTimelineAgent", () => { describe("useTimelineAgent", () => {
it("updates the intro message when target health changes", () => {
const demo = demoController();
const initialProps: { readonly status: PresentationTargetHealth } = {
status: checkingStatus,
};
const { result, rerender } = renderHook(
({ status }: { readonly status: PresentationTargetHealth }) =>
useTimelineAgent(demo, { mode: "live", status }),
{ initialProps },
);
expect(result.current.messages[0]?.parts).toEqual(
expect.arrayContaining([
expect.objectContaining({ text: "Live target configured, checking reachability." }),
]),
);
rerender({ status: readyStatus });
expect(result.current.messages[0]?.parts).toEqual(
expect.arrayContaining([
expect.objectContaining({
text: "Live target is ready. Direct slides still show replay evidence until I start the live run.",
}),
]),
);
});
it("starts the prepared workflow through the timeline", async () => { it("starts the prepared workflow through the timeline", async () => {
const start = vi.fn(); const start = vi.fn();
const demo = demoController({ start }); const demo = demoController({ start });
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { DemoTimelineController } from "../useDemoTimeline.js"; import type { DemoTimelineController } from "../useDemoTimeline.js";
import type { PresentationTargetHealth } from "../../presentation/presentation-target-status.js"; import type { PresentationTargetHealth } from "../../presentation/presentation-target-status.js";
import { import {
@@ -45,6 +45,13 @@ const appendToolMessage = (
}, },
]; ];
const introMessage = (text: string): AgentMessage =>
agentTextMessage(
"timeline-agent-intro",
"assistant",
text,
);
const introForStatus = (status: PresentationTargetHealth): string => { const introForStatus = (status: PresentationTargetHealth): string => {
switch (status.kind) { switch (status.kind) {
case "ready": case "ready":
@@ -68,15 +75,18 @@ export const useTimelineAgent = (
options.status.kind === "ready" || options.status.kind === "active" options.status.kind === "ready" || options.status.kind === "active"
? options.mode ? options.mode
: "replay"; : "replay";
const introText = introForStatus(options.status);
const [messages, setMessages] = useState<ReadonlyArray<AgentMessage>>([ const [messages, setMessages] = useState<ReadonlyArray<AgentMessage>>([
agentTextMessage( introMessage(introText),
"timeline-agent-intro",
"assistant",
introForStatus(options.status),
),
]); ]);
useEffect(() => {
setMessages((current) => current.map((message) =>
message.id === "timeline-agent-intro" ? introMessage(introText) : message,
));
}, [introText]);
const runLabel = modeLabel === "live" ? "Run prepared workflow" : "Run replay walkthrough"; const runLabel = modeLabel === "live" ? "Run prepared workflow" : "Run replay walkthrough";
const canRun = demo.canStart && !demo.inFlight && demo.state.phase !== "running"; const canRun = demo.canStart && !demo.inFlight && demo.state.phase !== "running";
@@ -134,4 +144,4 @@ export const useTimelineAgent = (
submitSelectedIssues, submitSelectedIssues,
cancelReview, cancelReview,
}; };
}; };
@@ -176,6 +176,16 @@ describe("PresentationRoute", () => {
expect(await screen.findByRole("button", { name: /run prepared workflow/i })).toBeInTheDocument(); expect(await screen.findByRole("button", { name: /run prepared workflow/i })).toBeInTheDocument();
}); });
it("updates the chat intro after the live health probe succeeds", async () => {
window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc");
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByText(/Live target is ready/i)).toBeInTheDocument();
expect(screen.getByLabelText("presentation evidence mode")).toHaveAttribute("data-status", "ready");
expect(screen.queryByText(/checking reachability/i)).not.toBeInTheDocument();
});
it("opens Scene 10 approval from the canonical hash", async () => { it("opens Scene 10 approval from the canonical hash", async () => {
window.location.hash = "#scene/interrupt-evidence/approval"; window.location.hash = "#scene/interrupt-evidence/approval";
const { PresentationRoute } = await import("./PresentationRoute.js"); const { PresentationRoute } = await import("./PresentationRoute.js");