mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Align UI context with chat turn lifecycle
This commit is contained in:
parent
aa04e55c52
commit
10b8a8473b
13 changed files with 122 additions and 159 deletions
|
|
@ -8,7 +8,7 @@ import { MessageScrollController, type MessageScrollIntent } from "./ui/scroll";
|
|||
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
||||
import { MarkdownMessageRenderer } from "./markdown-message-renderer";
|
||||
import { isRollbackCandidateItem, rollbackCandidateFromItems } from "./rollback";
|
||||
import { activeTurnId, chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
|
||||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
|
||||
import { unmountReactRoot } from "../../shared/ui/react-root";
|
||||
|
||||
export interface ChatMessageRendererOptions {
|
||||
|
|
@ -63,16 +63,14 @@ export class ChatMessageRenderer {
|
|||
this.messagesEl = messagesEl;
|
||||
const scrollPlan = this.scrollController.prepareRender(messagesEl, this.options.consumeScrollIntent());
|
||||
const busy = chatTurnBusy(state);
|
||||
const activeTurn = activeTurnId(state);
|
||||
const rollbackCandidate = busy ? null : rollbackCandidateFromItems(state.displayItems);
|
||||
const implementPlanCandidate = implementPlanCandidateFromState(state);
|
||||
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: state.activeThreadId,
|
||||
activeTurnId: activeTurn,
|
||||
turnLifecycle: state.turnLifecycle,
|
||||
historyCursor: state.historyCursor,
|
||||
loadingHistory: state.loadingHistory,
|
||||
busy,
|
||||
displayItems: state.displayItems,
|
||||
turnDiffs: state.turnDiffs,
|
||||
workspaceRoot: state.activeThreadCwd ?? this.options.vaultPath,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ export function agentDisplayItem(item: CollabAgentToolCallItem, turnId?: string)
|
|||
};
|
||||
}
|
||||
|
||||
export function activeAgentRunSummary(items: readonly DisplayItem[], activeTurnId: string | null, busy: boolean): AgentRunSummary | null {
|
||||
if (!busy || !activeTurnId) return null;
|
||||
export function activeAgentRunSummary(items: readonly DisplayItem[], activeTurnId: string | null): AgentRunSummary | null {
|
||||
if (!activeTurnId) return null;
|
||||
|
||||
const agentStatuses = new Map<string, AgentStateDisplay>();
|
||||
for (const item of items) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { displayBlocksForItems } from "../display/blocks";
|
|||
import { executionState } from "../display/state";
|
||||
import type { ToolResultDisplayItem } from "../display/tool-view";
|
||||
import type { DisplayBlock, DisplayDetailSection, DisplayItem } from "../display/types";
|
||||
import { activeTurnId, type ChatTurnLifecycleState } from "../chat-state";
|
||||
import { IconButton } from "../../../shared/ui/react-components";
|
||||
import { toolResultNode } from "./tool-result";
|
||||
import { activeAgentRunSummaryBlock, agentRunSummaryNode, workItemNode, type WorkItemDisplayItem } from "./work-items";
|
||||
|
|
@ -20,10 +21,9 @@ export interface MessageStreamBlock {
|
|||
|
||||
export interface MessageStreamContext {
|
||||
activeThreadId: string | null;
|
||||
activeTurnId: string | null;
|
||||
turnLifecycle: ChatTurnLifecycleState;
|
||||
historyCursor: string | null;
|
||||
loadingHistory: boolean;
|
||||
busy: boolean;
|
||||
displayItems: readonly DisplayItem[];
|
||||
turnDiffs?: ReadonlyMap<string, string>;
|
||||
workspaceRoot?: string | null;
|
||||
|
|
@ -42,6 +42,10 @@ export interface MessageStreamContext {
|
|||
renderPendingRequests?: () => ReactNode;
|
||||
}
|
||||
|
||||
export function messageStreamActiveTurnId(context: Pick<MessageStreamContext, "turnLifecycle">): string | null {
|
||||
return activeTurnId(context);
|
||||
}
|
||||
|
||||
type RenderableMessageItem = Extract<DisplayItem, { kind: "message" | "system" | "userInputResult" }>;
|
||||
|
||||
function isRenderableMessageItem(item: DisplayItem): item is RenderableMessageItem {
|
||||
|
|
@ -71,6 +75,7 @@ function displayItemNode(item: DisplayItem, context: MessageStreamContext): Reac
|
|||
|
||||
export function messageStreamBlocks(context: MessageStreamContext): MessageStreamBlock[] {
|
||||
const blocks: MessageStreamBlock[] = [];
|
||||
const activeTurn = messageStreamActiveTurnId(context);
|
||||
|
||||
if (context.activeThreadId && context.historyCursor) {
|
||||
blocks.push({
|
||||
|
|
@ -87,7 +92,7 @@ export function messageStreamBlocks(context: MessageStreamContext): MessageStrea
|
|||
return blocks;
|
||||
}
|
||||
|
||||
for (const block of displayBlocksForItems(context.displayItems, context.activeTurnId, context.workspaceRoot, context.turnDiffs)) {
|
||||
for (const block of displayBlocksForItems(context.displayItems, activeTurn, context.workspaceRoot, context.turnDiffs)) {
|
||||
if (block.type === "item") {
|
||||
blocks.push({
|
||||
key: `item:${block.item.id}`,
|
||||
|
|
@ -104,7 +109,7 @@ export function messageStreamBlocks(context: MessageStreamContext): MessageStrea
|
|||
const agentSummary = activeAgentRunSummaryBlock(context);
|
||||
if (agentSummary) {
|
||||
blocks.push({
|
||||
key: `active-agents:${context.activeTurnId ?? "none"}`,
|
||||
key: `active-agents:${activeTurn ?? "none"}`,
|
||||
node: agentRunSummaryNode(agentSummary),
|
||||
});
|
||||
}
|
||||
|
|
@ -587,9 +592,10 @@ function displayRoleLabel(item: DisplayItem): string {
|
|||
return "System";
|
||||
}
|
||||
|
||||
function isMessageCopyActionVisible(item: DisplayItem, context: Pick<MessageStreamContext, "busy" | "activeTurnId">): boolean {
|
||||
function isMessageCopyActionVisible(item: DisplayItem, context: Pick<MessageStreamContext, "turnLifecycle">): boolean {
|
||||
if (item.kind !== "message" || item.copyText === undefined) return false;
|
||||
return !(context.busy && context.activeTurnId && item.role === "assistant" && item.turnId === context.activeTurnId);
|
||||
const activeTurn = messageStreamActiveTurnId(context);
|
||||
return !(activeTurn && item.role === "assistant" && item.turnId === activeTurn);
|
||||
}
|
||||
|
||||
function messageClass(item: DisplayItem): string {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { activeAgentRunSummary } from "../display/agent";
|
|||
import { executionState } from "../display/state";
|
||||
import type { AgentDisplayItem, AgentRunSummary, AgentRunSummaryAgent, TaskProgressDisplayItem, ToolDisplayItem } from "../display/types";
|
||||
import { agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel, taskStatusMarker } from "../display/labels";
|
||||
import type { MessageStreamContext } from "./message-stream";
|
||||
import { messageStreamActiveTurnId, type MessageStreamContext } from "./message-stream";
|
||||
import { createWorkMessageClassName } from "./work-message";
|
||||
import { shortThreadId } from "../../../utils";
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ type ReasoningDisplayItem = ToolDisplayItem & { kind: "reasoning" };
|
|||
export type WorkItemDisplayItem = TaskProgressDisplayItem | AgentDisplayItem | ReasoningDisplayItem;
|
||||
|
||||
export function activeAgentRunSummaryBlock(context: MessageStreamContext): AgentRunSummary | null {
|
||||
return activeAgentRunSummary(context.displayItems, context.activeTurnId, context.busy);
|
||||
return activeAgentRunSummary(context.displayItems, messageStreamActiveTurnId(context));
|
||||
}
|
||||
|
||||
export function agentRunSummaryNode(summary: AgentRunSummary): ReactNode {
|
||||
|
|
@ -229,8 +229,9 @@ function isLongAgentMessage(message: string): boolean {
|
|||
}
|
||||
|
||||
function isReasoningActive(item: ReasoningDisplayItem, context: MessageStreamContext): boolean {
|
||||
if (!context.busy || !context.activeTurnId || item.turnId !== context.activeTurnId) return false;
|
||||
const activeTurn = messageStreamActiveTurnId(context);
|
||||
if (!activeTurn || item.turnId !== activeTurn) return false;
|
||||
if (executionState(item) === "completed") return false;
|
||||
const latestActiveTurnItem = [...context.displayItems].reverse().find((candidate) => candidate.turnId === context.activeTurnId);
|
||||
const latestActiveTurnItem = [...context.displayItems].reverse().find((candidate) => candidate.turnId === activeTurn);
|
||||
return latestActiveTurnItem?.id === item.id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,8 +388,7 @@ export class CodexChatView extends ItemView {
|
|||
return {
|
||||
viewId: this.viewId,
|
||||
threadId: this.closing ? null : this.state.activeThreadId,
|
||||
busy: this.turnBusy,
|
||||
activeTurnId: this.activeTurnId,
|
||||
turnLifecycle: openPanelTurnLifecycle(this.state.turnLifecycle),
|
||||
pendingApprovals: this.state.approvals.length,
|
||||
pendingUserInputs: this.state.pendingUserInputs.length,
|
||||
hasComposerDraft: this.state.composerDraft.trim().length > 0,
|
||||
|
|
@ -1722,6 +1721,12 @@ export class CodexChatView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
function openPanelTurnLifecycle(state: ChatState["turnLifecycle"]): OpenCodexPanelSnapshot["turnLifecycle"] {
|
||||
if (state.kind === "running") return { kind: "running", turnId: state.turnId };
|
||||
if (state.kind === "starting") return { kind: "starting" };
|
||||
return { kind: "idle" };
|
||||
}
|
||||
|
||||
function latestProposedPlanItem(items: readonly DisplayItem[]): DisplayItem | null {
|
||||
return [...items].reverse().find((item) => item.kind === "message" && item.role === "assistant" && item.proposedPlan === true) ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function snapshotsForThreads(snapshots: OpenCodexPanelSnapshot[]): Map<string, O
|
|||
function snapshotStatus(snapshot: OpenCodexPanelSnapshot): ThreadsLiveStatus {
|
||||
if (snapshot.pendingUserInputs > 0) return "needs-input";
|
||||
if (snapshot.pendingApprovals > 0) return "approval";
|
||||
if (snapshot.busy) return "running";
|
||||
if (snapshot.turnLifecycle.kind !== "idle") return "running";
|
||||
if (snapshot.hasComposerDraft) return "draft";
|
||||
if (!snapshot.connected) return "offline";
|
||||
return "open";
|
||||
|
|
|
|||
|
|
@ -519,8 +519,7 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
function isIdleEmptyPanelSnapshot(snapshot: OpenCodexPanelSnapshot): boolean {
|
||||
return (
|
||||
snapshot.threadId === null &&
|
||||
!snapshot.busy &&
|
||||
snapshot.activeTurnId === null &&
|
||||
snapshot.turnLifecycle.kind === "idle" &&
|
||||
snapshot.pendingApprovals === 0 &&
|
||||
snapshot.pendingUserInputs === 0 &&
|
||||
!snapshot.hasComposerDraft
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
export type OpenCodexPanelTurnLifecycle = { kind: "idle" } | { kind: "starting" } | { kind: "running"; turnId: string };
|
||||
|
||||
export interface OpenCodexPanelSnapshot {
|
||||
viewId: string;
|
||||
threadId: string | null;
|
||||
busy: boolean;
|
||||
activeTurnId: string | null;
|
||||
turnLifecycle: OpenCodexPanelTurnLifecycle;
|
||||
pendingApprovals: number;
|
||||
pendingUserInputs: number;
|
||||
hasComposerDraft: boolean;
|
||||
|
|
|
|||
|
|
@ -1064,7 +1064,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
},
|
||||
];
|
||||
|
||||
expect(activeAgentRunSummary(items, "t1", true)).toEqual({
|
||||
expect(activeAgentRunSummary(items, "t1")).toEqual({
|
||||
running: 1,
|
||||
completed: 1,
|
||||
failed: 1,
|
||||
|
|
@ -1075,7 +1075,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
],
|
||||
additionalAgents: 0,
|
||||
});
|
||||
expect(activeAgentRunSummary(items, "t1", false)).toBeNull();
|
||||
expect(activeAgentRunSummary(items, null)).toBeNull();
|
||||
});
|
||||
|
||||
it("summarizes active subagent previews and fallback receiver states", () => {
|
||||
|
|
@ -1116,7 +1116,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
},
|
||||
];
|
||||
|
||||
expect(activeAgentRunSummary(items, "t1", true)).toMatchObject({
|
||||
expect(activeAgentRunSummary(items, "t1")).toMatchObject({
|
||||
running: 3,
|
||||
completed: 0,
|
||||
failed: 1,
|
||||
|
|
@ -1148,7 +1148,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
},
|
||||
];
|
||||
|
||||
expect(activeAgentRunSummary(items, "t1", true)).toBeNull();
|
||||
expect(activeAgentRunSummary(items, "t1")).toBeNull();
|
||||
});
|
||||
|
||||
it("adds edited files to the final assistant message", () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { PendingUserInput } from "../../../../src/features/chat/user-input/
|
|||
import { pendingRequestMessageNode, type PendingRequestMessageActions } from "../../../../src/features/chat/ui/pending-request-message";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
import { implementPlanCandidateFromState } from "../../../../src/features/chat/chat-message-renderer";
|
||||
import type { ChatTurnLifecycleState } from "../../../../src/features/chat/chat-state";
|
||||
import { messageStreamBlocks as rawMessageStreamBlocks, renderMessageStreamBlocks } from "../../../../src/features/chat/ui/message-stream";
|
||||
import { changeInputValue, installObsidianDomShims, topLevelDetailsSummaries } from "./dom-test-helpers";
|
||||
import { renderReactRoot, unmountReactRoot } from "../../../../src/shared/ui/react-root";
|
||||
|
|
@ -69,6 +70,18 @@ function pendingRequestActions(overrides: Partial<PendingRequestMessageActions>
|
|||
};
|
||||
}
|
||||
|
||||
function idleTurnLifecycle(): ChatTurnLifecycleState {
|
||||
return { kind: "idle" };
|
||||
}
|
||||
|
||||
function runningTurnLifecycle(turnId = "turn"): ChatTurnLifecycleState {
|
||||
return { kind: "running", turnId };
|
||||
}
|
||||
|
||||
function startingTurnLifecycle(): ChatTurnLifecycleState {
|
||||
return { kind: "starting", pendingTurnStart: { anchorItemId: "local-user", promptSubmitHookItemIds: [] } };
|
||||
}
|
||||
|
||||
function withMessageContentScrollHeight<T>(scrollHeight: number, fn: () => T): T {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollHeight");
|
||||
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
|
||||
|
|
@ -139,10 +152,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const parent = document.createElement("div");
|
||||
const baseContext = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }),
|
||||
|
|
@ -199,10 +211,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const loadOlderTurns = vi.fn();
|
||||
const [historyBlock] = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: "cursor",
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns,
|
||||
|
|
@ -229,10 +240,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders the empty message stream state as a React block", () => {
|
||||
const [emptyBlock] = messageStreamBlocks({
|
||||
activeThreadId: null,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -254,10 +264,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders review result items as compact auto-review tool rows", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "review-1", kind: "reviewResult", role: "tool", text: "Auto-review denied this command.", markdown: false }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -277,10 +286,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders review result details inside one auto-review details block", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "review-1",
|
||||
|
|
@ -334,10 +342,9 @@ describe("message stream block identity and message actions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "cmd-1",
|
||||
|
|
@ -384,10 +391,9 @@ describe("message stream block identity and message actions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
workspaceRoot: "/vault",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -415,10 +421,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders structured system result details as visible selectable meta rows", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "system-help",
|
||||
|
|
@ -461,10 +466,9 @@ describe("message stream block identity and message actions", () => {
|
|||
] as const;
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [...items],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -488,10 +492,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const copyText = vi.fn();
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{ id: "u1", kind: "message", role: "user", text: "rendered user", copyText: "**user**", turnId: "turn-1", markdown: true },
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "rendered answer", copyText: "# Answer", turnId: "turn-1", markdown: true },
|
||||
|
|
@ -524,10 +527,9 @@ describe("message stream block identity and message actions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "p1",
|
||||
|
|
@ -569,10 +571,9 @@ describe("message stream block identity and message actions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "a1", kind: "message", role: "assistant", text: "**answer**", turnId: "turn-1", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -590,10 +591,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const parent = document.createElement("div");
|
||||
const baseContext = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text: `markdown:${text}` }),
|
||||
|
|
@ -627,10 +627,9 @@ describe("message stream block identity and message actions", () => {
|
|||
});
|
||||
const baseContext = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown,
|
||||
|
|
@ -668,10 +667,9 @@ describe("message stream block identity and message actions", () => {
|
|||
});
|
||||
const context = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "**answer**", turnId: "turn-1", markdown: true },
|
||||
] satisfies DisplayItem[],
|
||||
|
|
@ -701,10 +699,9 @@ describe("message stream block identity and message actions", () => {
|
|||
} as const;
|
||||
const context = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn-1",
|
||||
turnLifecycle: runningTurnLifecycle("turn-1"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [item],
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -714,7 +711,7 @@ describe("message stream block identity and message actions", () => {
|
|||
};
|
||||
|
||||
const runningBlock = messageStreamBlocks(context)[0];
|
||||
const completedBlock = messageStreamBlocks({ ...context, busy: false, activeTurnId: null })[0];
|
||||
const completedBlock = messageStreamBlocks({ ...context, turnLifecycle: idleTurnLifecycle() })[0];
|
||||
|
||||
expect(renderMessageBlockElement(runningBlock).querySelector(".codex-panel__copy-message")).toBeNull();
|
||||
expect(renderMessageBlockElement(completedBlock).querySelector(".codex-panel__copy-message")).not.toBeNull();
|
||||
|
|
@ -724,10 +721,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const onImplementPlanItem = vi.fn();
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "p1",
|
||||
|
|
@ -791,10 +787,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("does not render copy actions for tool items", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "tool-1",
|
||||
|
|
@ -820,10 +815,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const onRollbackItem = vi.fn();
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "u1", kind: "message", role: "user", text: "latest", copyText: "latest", turnId: "turn-1", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -855,10 +849,9 @@ describe("message stream block identity and message actions", () => {
|
|||
});
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{ id: "u1", kind: "message", role: "user", text: "visible text", copyText: "full copied text", turnId: "turn-1", markdown: true },
|
||||
],
|
||||
|
|
@ -898,10 +891,9 @@ describe("message stream block identity and message actions", () => {
|
|||
withMessageContentScrollHeight(120, () => {
|
||||
const shortUserBlock = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "u1", kind: "message", role: "user", text: "short", turnId: "turn-1", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -916,10 +908,9 @@ describe("message stream block identity and message actions", () => {
|
|||
withMessageContentScrollHeight(500, () => {
|
||||
const assistantBlock = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "a1", kind: "message", role: "assistant", text: "long", turnId: "turn-1", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -935,10 +926,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("does not render rollback action when no item is eligible", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: startingTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [{ id: "u1", kind: "message", role: "user", text: "running", turnId: "turn-1", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -954,10 +944,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders command items as a compact summary with output behind details", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "cmd-1",
|
||||
|
|
@ -993,10 +982,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("omits command exit and duration rows while they are unavailable", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "cmd-1",
|
||||
|
|
@ -1029,10 +1017,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("uses read as the command header for parsed file reads", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "cmd-1",
|
||||
|
|
@ -1062,10 +1049,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders file diffs inside a single file change details block", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
workspaceRoot: "/vault/project",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -1101,10 +1087,9 @@ describe("message stream block identity and message actions", () => {
|
|||
const openTurnDiff = vi.fn();
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
workspaceRoot: "/vault/project",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -1145,10 +1130,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders referenced thread metadata without exposing hidden context", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "u1",
|
||||
|
|
@ -1182,10 +1166,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("renders resolved file mentions as a collapsed user message attachment", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "u1",
|
||||
|
|
@ -1214,10 +1197,9 @@ describe("message stream block identity and message actions", () => {
|
|||
it("does not render the open diff action without aggregated turn diff", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "patch-1",
|
||||
|
|
@ -1248,10 +1230,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders generic tool details as visible sections inside one details block", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "tool-1",
|
||||
|
|
@ -1288,10 +1269,9 @@ describe("work log renderer decisions", () => {
|
|||
it("keeps the tool summary as a separate row when details are open", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "cmd-1",
|
||||
|
|
@ -1323,10 +1303,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders generic tools without details as two compact rows", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "tool-plain",
|
||||
|
|
@ -1353,10 +1332,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders path summary tools relative to the workspace root", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
workspaceRoot: "/vault/project",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -1394,10 +1372,9 @@ describe("work log renderer decisions", () => {
|
|||
} as const;
|
||||
const baseContext = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [item] satisfies DisplayItem[],
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -1416,10 +1393,9 @@ describe("work log renderer decisions", () => {
|
|||
it("keeps path summary tools absolute outside the workspace root", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
workspaceRoot: "/vault/project",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -1446,10 +1422,9 @@ describe("work log renderer decisions", () => {
|
|||
it("does not treat generic tool summaries as paths without an explicit marker", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
workspaceRoot: "/vault/project",
|
||||
displayItems: [
|
||||
{
|
||||
|
|
@ -1475,10 +1450,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders hook metadata as rows inside one details block", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "hook-1",
|
||||
|
|
@ -1523,10 +1497,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders hook metadata when the hook is inside a completed-turn activity group", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{ id: "u1", kind: "message", role: "user", text: "do it", turnId: "turn", markdown: true },
|
||||
{
|
||||
|
|
@ -1575,10 +1548,9 @@ describe("work log renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{ id: "u1", kind: "message", role: "user", text: "do it", turnId: "turn", markdown: true },
|
||||
{
|
||||
|
|
@ -1643,10 +1615,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders task progress items as a dedicated task list", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "plan-progress-turn",
|
||||
|
|
@ -1684,10 +1655,9 @@ describe("work log renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "plan-progress-turn",
|
||||
|
|
@ -1720,10 +1690,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders agent activity with target state and prompt details", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -1766,10 +1735,9 @@ describe("work log renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -1814,10 +1782,9 @@ describe("work log renderer decisions", () => {
|
|||
const onDetailsToggle = vi.fn();
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -1860,10 +1827,9 @@ describe("work log renderer decisions", () => {
|
|||
it("renders a compact live agent summary while subagents are running", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -1907,10 +1873,9 @@ describe("work log renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -1951,10 +1916,9 @@ describe("work log renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [{ id: "reasoning-1", kind: "reasoning", role: "tool", text: "", turnId: "turn", status: "running" }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -1975,10 +1939,9 @@ describe("work log renderer decisions", () => {
|
|||
it("hides the live agent summary once all subagents are complete", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -2008,10 +1971,9 @@ describe("work log renderer decisions", () => {
|
|||
it("marks the live agent summary failed when any subagent fails", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: runningTurnLifecycle("turn"),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: true,
|
||||
displayItems: [
|
||||
{
|
||||
id: "agent-1",
|
||||
|
|
@ -2357,10 +2319,9 @@ describe("pending request renderer decisions", () => {
|
|||
it("renders submitted user input separately from approvals", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "user-input-submitted-1",
|
||||
|
|
@ -2390,10 +2351,9 @@ describe("pending request renderer decisions", () => {
|
|||
it("renders manual approval results with completion state and details", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: "turn",
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "approval-1",
|
||||
|
|
@ -2436,10 +2396,9 @@ describe("pending request renderer decisions", () => {
|
|||
it("renders auto-review summaries under the final assistant message", () => {
|
||||
const block = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [
|
||||
{
|
||||
id: "assistant-1",
|
||||
|
|
@ -2466,10 +2425,9 @@ describe("pending request renderer decisions", () => {
|
|||
it("adds pending requests to the bottom of message stream blocks", () => {
|
||||
const blocks = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "a1", kind: "message", role: "assistant", text: "Done", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -2492,10 +2450,9 @@ describe("pending request renderer decisions", () => {
|
|||
parent,
|
||||
messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "a1", kind: "message", role: "assistant", text: "Waiting", markdown: true }],
|
||||
openDetails: new Set(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
@ -2527,10 +2484,9 @@ describe("pending request renderer decisions", () => {
|
|||
const parent = document.createElement("div");
|
||||
const baseContext = {
|
||||
activeThreadId: "thread",
|
||||
activeTurnId: null,
|
||||
turnLifecycle: idleTurnLifecycle(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
busy: false,
|
||||
displayItems: [{ id: "a1", kind: "message", role: "assistant", text: "Done", markdown: true }] satisfies DisplayItem[],
|
||||
openDetails: new Set<string>(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { changeInputValue, composerSuggestionScrollFixture, installObsidianDomSh
|
|||
import { renderThreadsView } from "../../../../src/features/threads-view/renderer";
|
||||
import { liveStateForSnapshots, threadRows, type ThreadsRowModel } from "../../../../src/features/threads-view/state";
|
||||
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
|
||||
import type { OpenCodexPanelSnapshot } from "../../../../src/runtime/open-panel-snapshot";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
|
|
@ -41,19 +42,17 @@ function openPanelSnapshot(
|
|||
overrides: Partial<{
|
||||
viewId: string;
|
||||
threadId: string | null;
|
||||
busy: boolean;
|
||||
activeTurnId: string | null;
|
||||
turnLifecycle: OpenCodexPanelSnapshot["turnLifecycle"];
|
||||
pendingApprovals: number;
|
||||
pendingUserInputs: number;
|
||||
hasComposerDraft: boolean;
|
||||
connected: boolean;
|
||||
}> = {},
|
||||
) {
|
||||
): OpenCodexPanelSnapshot {
|
||||
return {
|
||||
viewId: "view",
|
||||
threadId: "thread",
|
||||
busy: false,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
|
|
@ -623,7 +622,7 @@ describe("threads view renderer decisions", () => {
|
|||
expect(
|
||||
liveStateForSnapshots([
|
||||
openPanelSnapshot({ viewId: "open", threadId: "thread" }),
|
||||
openPanelSnapshot({ viewId: "running", threadId: "thread", busy: true }),
|
||||
openPanelSnapshot({ viewId: "running", threadId: "thread", turnLifecycle: { kind: "running", turnId: "turn" } }),
|
||||
openPanelSnapshot({ viewId: "approval", threadId: "thread", pendingApprovals: 1 }),
|
||||
openPanelSnapshot({ viewId: "input", threadId: "thread", pendingUserInputs: 1 }),
|
||||
]),
|
||||
|
|
@ -637,7 +636,9 @@ describe("threads view renderer decisions", () => {
|
|||
status: "offline",
|
||||
label: "Offline",
|
||||
});
|
||||
expect(liveStateForSnapshots([openPanelSnapshot({ viewId: "none", threadId: null, busy: true })])).toBeNull();
|
||||
expect(
|
||||
liveStateForSnapshots([openPanelSnapshot({ viewId: "none", threadId: null, turnLifecycle: { kind: "running", turnId: "turn" } })]),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("renders thread rows with live state and routes open actions", () => {
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
await submit;
|
||||
|
||||
expect((view as unknown as { state: ChatState }).state.turnLifecycle).toEqual({ kind: "idle" });
|
||||
expect(view.openPanelSnapshot()).toMatchObject({ busy: false, activeTurnId: null });
|
||||
expect(view.openPanelSnapshot()).toMatchObject({ turnLifecycle: { kind: "idle" } });
|
||||
});
|
||||
|
||||
it("requests a workspace layout save after resuming a thread", async () => {
|
||||
|
|
@ -352,7 +352,7 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
|
||||
expect(client.startThread).not.toHaveBeenCalled();
|
||||
expect(view.getState()).toEqual({ version: 1 });
|
||||
expect(view.openPanelSnapshot()).toMatchObject({ threadId: null, busy: false, activeTurnId: null, hasComposerDraft: false });
|
||||
expect(view.openPanelSnapshot()).toMatchObject({ threadId: null, turnLifecycle: { kind: "idle" }, hasComposerDraft: false });
|
||||
expect(requestSaveLayout).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
vi.spyOn(openView, "openPanelSnapshot").mockReturnValue({
|
||||
viewId: "open-view",
|
||||
threadId: "thread-1",
|
||||
busy: false,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
|
|
@ -98,8 +97,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
vi.spyOn(busyView, "openPanelSnapshot").mockReturnValue({
|
||||
viewId: "busy-view",
|
||||
threadId: "other-thread",
|
||||
busy: false,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
|
|
@ -111,8 +109,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
vi.spyOn(emptyView, "openPanelSnapshot").mockReturnValue({
|
||||
viewId: "empty-view",
|
||||
threadId: null,
|
||||
busy: false,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
|
|
@ -451,8 +448,7 @@ function panelSnapshot(
|
|||
return {
|
||||
viewId: "view",
|
||||
threadId: "thread",
|
||||
busy: false,
|
||||
activeTurnId: null,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
|
|
|
|||
Loading…
Reference in a new issue