Show live task progress while agents work (#3227)
* feat(agents): surface live task progress Keep current task state above the composer and turn task snapshots into semantic timeline activity. Normalize provider task protocols while preserving genuine plan flows. * fix(agents): admit tasks through stream sequencing * test(agents): expect normalized task status
This commit is contained in:
@@ -80,6 +80,62 @@ function permutations<T>(values: readonly T[]): T[][] {
|
||||
);
|
||||
}
|
||||
|
||||
function taskTexts(tasks: ReadonlyArray<{ text: string }>): string[] {
|
||||
return tasks.map((task) => task.text);
|
||||
}
|
||||
|
||||
describe("agent task state", () => {
|
||||
it("notifies the task subscriber only when its task snapshot changes", () => {
|
||||
initializeTestSession();
|
||||
const snapshots: string[][] = [];
|
||||
const unsubscribe = useSessionStore.subscribe(
|
||||
(state) => state.sessions["test-server"]?.agentTasks.get("agent-1") ?? [],
|
||||
(tasks) => snapshots.push(taskTexts(tasks)),
|
||||
);
|
||||
|
||||
const tasks = [{ id: "1", text: "Inspect", status: "pending" as const, completed: false }];
|
||||
useSessionStore
|
||||
.getState()
|
||||
.setAgentStreamState("test-server", "agent-1", { taskSnapshot: tasks });
|
||||
useSessionStore.getState().setMessages("test-server", []);
|
||||
useSessionStore
|
||||
.getState()
|
||||
.setAgentStreamState("test-server", "agent-1", { taskSnapshot: [...tasks] });
|
||||
useSessionStore.getState().setAgentStreamState("test-server", "agent-1", {
|
||||
taskSnapshot: [{ ...tasks[0], status: "completed", completed: true }],
|
||||
});
|
||||
unsubscribe();
|
||||
|
||||
expect(snapshots).toEqual([["Inspect"], ["Inspect"]]);
|
||||
});
|
||||
|
||||
it("restores the latest task snapshot from an authoritative timeline", () => {
|
||||
initializeTestSession();
|
||||
const todo: StreamItem = {
|
||||
kind: "todo_list",
|
||||
id: "todo-1",
|
||||
provider: "codex",
|
||||
timestamp: new Date("2026-08-11T10:00:00.000Z"),
|
||||
activity: { type: "started", task: "Verify" },
|
||||
items: [{ text: "Verify", status: "in_progress", completed: false }],
|
||||
};
|
||||
|
||||
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
|
||||
items: [todo],
|
||||
head: [],
|
||||
range: null,
|
||||
older: "none",
|
||||
newer: false,
|
||||
synchronized: true,
|
||||
acknowledgedClientMessageIds: [],
|
||||
});
|
||||
|
||||
expect(useSessionStore.getState().sessions["test-server"]?.agentTasks.get("agent-1")).toEqual(
|
||||
todo.items,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent timeline state", () => {
|
||||
it("commits canonical items, range, and older availability as one synced state", () => {
|
||||
initializeTestSession();
|
||||
|
||||
Reference in New Issue
Block a user