murashit_codex-panel/tests/features/chat/application/state/subagent-activity.test.ts

136 lines
4.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
initialSubagentActivityState,
reduceSubagentActivitySlice,
} from "../../../../../src/features/chat/application/state/subagent-activity";
import type { ThreadStreamItem } from "../../../../../src/features/chat/domain/thread-stream/items";
describe("subagent activity state", () => {
it("tracks a child and accumulates its active reasoning summary", () => {
let state = reduceSubagentActivitySlice(initialSubagentActivityState("parent-turn"), {
type: "subagent-activity/tracked",
threadId: "child",
parentTurnId: "parent-turn",
});
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/turn-started",
threadId: "child",
childTurnId: "child-turn",
});
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/text-delta-appended",
threadId: "child",
childTurnId: "child-turn",
itemId: "reasoning",
label: "reasoning",
delta: "Inspecting ",
kind: "reasoning",
});
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/text-delta-appended",
threadId: "child",
childTurnId: "child-turn",
itemId: "reasoning",
label: "reasoning",
delta: "routing",
kind: "reasoning",
});
expect(state.byThreadId.get("child")).toMatchObject({
childTurnId: "child-turn",
latestItem: { id: "reasoning", kind: "reasoning", text: "reasoning: Inspecting routing" },
});
});
it("does not let an older item completion replace a newer activity", () => {
let state = trackedState();
state = observe(state, reasoningItem("older", "Reading files"), true);
state = observe(state, reasoningItem("newer", "Running tests"), true);
state = observe(state, { ...reasoningItem("older", "Finished reading"), executionState: "completed" }, false);
expect(state.byThreadId.get("child")?.latestItem).toMatchObject({
id: "newer",
text: "Running tests",
});
});
it("uses the last displayable canonical item when the child turn completes", () => {
let state = trackedState();
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/turn-completed",
threadId: "child",
childTurnId: "child-turn",
items: [
{ id: "user", kind: "dialogue", dialogueKind: "user", role: "user", text: "work" },
reasoningItem("reasoning", "Done checking"),
{
id: "answer",
kind: "dialogue",
dialogueKind: "assistantResponse",
dialogueState: "completed",
role: "assistant",
text: "Everything passes.",
},
],
executionState: "completed",
});
expect(state.byThreadId.get("child")?.latestItem).toMatchObject({
id: "answer",
text: "Everything passes.",
});
});
it("ignores delayed notifications from an older child turn", () => {
let state = trackedState();
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/turn-started",
threadId: "child",
childTurnId: "new-turn",
});
state = observe(state, reasoningItem("current", "Current work", "new-turn"), true);
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/text-delta-appended",
threadId: "child",
childTurnId: "old-turn",
itemId: "stale",
label: "reasoning",
delta: "Older work",
kind: "reasoning",
});
state = reduceSubagentActivitySlice(state, {
type: "subagent-activity/turn-completed",
threadId: "child",
childTurnId: "old-turn",
items: [reasoningItem("stale", "Older work", "old-turn")],
executionState: "completed",
});
expect(state.byThreadId.get("child")).toMatchObject({
childTurnId: "new-turn",
executionState: "running",
latestItem: { id: "current", text: "Current work" },
});
});
});
function trackedState() {
return reduceSubagentActivitySlice(initialSubagentActivityState("parent-turn"), {
type: "subagent-activity/tracked",
threadId: "child",
parentTurnId: "parent-turn",
});
}
function observe(state: ReturnType<typeof trackedState>, item: ThreadStreamItem, advance: boolean): ReturnType<typeof trackedState> {
return reduceSubagentActivitySlice(state, {
type: "subagent-activity/item-observed",
threadId: "child",
item,
advance,
});
}
function reasoningItem(id: string, text: string, turnId = "child-turn"): ThreadStreamItem {
return { id, kind: "reasoning", role: "tool", turnId, text };
}