feat: support explicit live demo launch

This commit is contained in:
lda
2026-07-12 10:49:56 +07:00 Verified
parent ad8c011351
commit b1fed7ebf8
4 changed files with 47 additions and 5 deletions
@@ -109,6 +109,37 @@ describe("useTimelineAgent", () => {
expect(start).toHaveBeenCalledWith("replay");
});
it("starts live explicitly from a direct replay view when the target is ready", async () => {
const start = vi.fn();
const demo = demoController({
state: { ...initialDemoTimelineState, mode: "replay" },
start,
});
const { result } = renderHook(() => useTimelineAgent(demo, {
mode: "live",
status: replayStatus,
liveTargetReady: true,
}));
expect(result.current.canRunLive).toBe(true);
await act(async () => result.current.runPreparedWorkflow("live"));
expect(start).toHaveBeenCalledWith("live");
});
it("does not offer an explicit live launch when health has failed", () => {
const start = vi.fn();
const demo = demoController({ start });
const { result } = renderHook(() => useTimelineAgent(demo, {
mode: "live",
status: failedStatus,
liveTargetReady: false,
}));
expect(result.current.canRunLive).toBe(false);
});
it("submits selected issues from the current interrupt payload", async () => {
const submitSelectedIssues = vi.fn(async () => {});
const demo = demoController({
@@ -14,13 +14,15 @@ export type TimelineAgentMode = "live" | "replay";
export type TimelineAgentOptions = {
readonly mode: TimelineAgentMode;
readonly status: PresentationTargetHealth;
readonly liveTargetReady?: boolean | undefined;
};
export type TimelineAgentController = {
readonly messages: ReadonlyArray<AgentMessage>;
readonly canRun: boolean;
readonly canRunLive: boolean;
readonly runLabel: string;
readonly runPreparedWorkflow: () => Promise<void>;
readonly runPreparedWorkflow: (mode?: TimelineAgentMode) => Promise<void>;
readonly submitSelectedIssues: () => Promise<void>;
readonly requestRevision: () => Promise<void>;
};
@@ -89,18 +91,23 @@ export const useTimelineAgent = (
const runLabel = modeLabel === "live" ? "Run prepared workflow" : "Run replay walkthrough";
const canRun = demo.canStart && !demo.inFlight && demo.state.phase !== "running";
const canRunLive = (options.liveTargetReady ?? (options.mode === "live" && (
options.status.kind === "ready" || options.status.kind === "active"
))) && !demo.inFlight && demo.state.phase !== "running";
const runPreparedWorkflow = useCallback(async () => {
const runPreparedWorkflow = useCallback(async (requestedMode?: TimelineAgentMode) => {
const mode = requestedMode ?? modeLabel;
if (mode === "live" && !canRunLive) return;
if (!demo.canStart || demo.inFlight || demo.state.phase === "running") return;
demo.start(modeLabel);
demo.start(mode);
setMessages((current) => appendToolMessage(
current,
"timeline-agent-start",
"startRun",
{ mode: modeLabel },
{ mode },
{ phase: "started" },
));
}, [demo, modeLabel]);
}, [canRunLive, demo, modeLabel]);
const selectedIssueIds = useMemo(
() => demo.interruptPayload?.proposed_issues.map((issue) => issue.id) ?? [],
@@ -135,6 +142,7 @@ export const useTimelineAgent = (
return {
messages,
canRun,
canRunLive,
runLabel,
runPreparedWorkflow,
submitSelectedIssues,