mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Split display item routing
This commit is contained in:
parent
a81b2cc3cb
commit
31281ae137
14 changed files with 353 additions and 269 deletions
|
|
@ -1,6 +1,29 @@
|
|||
import type { DisplayItem } from "./types";
|
||||
import type { TurnPlanStep } from "../generated/app-server/v2/TurnPlanStep";
|
||||
import { taskStatusMarker } from "./labels";
|
||||
import { classifyExecutionState } from "./state";
|
||||
|
||||
export function normalizeProposedPlanMarkdown(text: string): string {
|
||||
return text
|
||||
.replace(/^\s*<proposed_plan>\s*\n?/i, "")
|
||||
.replace(/\n?\s*<\/proposed_plan>\s*$/i, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function planProgressDisplayItem(turnId: string, explanation: string | null, plan: TurnPlanStep[]): DisplayItem {
|
||||
const lines = plan.map((step) => `${taskStatusMarker(step.status)} ${step.step}`);
|
||||
const body = [explanation?.trim(), ...lines].filter((line): line is string => Boolean(line && line.length > 0)).join("\n");
|
||||
const status = plan.some((step) => step.status === "inProgress" || step.status === "pending") ? "inProgress" : "completed";
|
||||
return {
|
||||
id: `plan-progress-${turnId}`,
|
||||
kind: "taskProgress",
|
||||
role: "tool",
|
||||
text: body || "Plan updated",
|
||||
turnId,
|
||||
itemId: `plan-progress-${turnId}`,
|
||||
explanation: explanation?.trim() || null,
|
||||
steps: plan.map((step) => ({ step: step.step, status: step.status })),
|
||||
status,
|
||||
state: classifyExecutionState({ status }),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { executionState } from "./model";
|
||||
import { executionState } from "./state";
|
||||
import type { DisplayItem } from "./types";
|
||||
|
||||
export interface DisplayItemSignatureContext {
|
||||
|
|
|
|||
10
src/display/system.ts
Normal file
10
src/display/system.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import type { DisplayItem } from "./types";
|
||||
|
||||
export function createSystemItem(text: string): DisplayItem {
|
||||
return {
|
||||
id: `system-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
kind: "system",
|
||||
role: "system",
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import type { DisplayDetailSection, DisplayFileChange, DisplayItem } from "./types";
|
||||
import type { ThreadItem } from "../generated/app-server/v2/ThreadItem";
|
||||
import type { Turn } from "../generated/app-server/v2/Turn";
|
||||
import type { TurnPlanStep } from "../generated/app-server/v2/TurnPlanStep";
|
||||
import { inputToText, truncate } from "../utils";
|
||||
import { taskStatusMarker } from "./labels";
|
||||
import { agentDisplayItem } from "./agent";
|
||||
import { pathRelativeToRoot } from "./paths";
|
||||
import { normalizeProposedPlanMarkdown } from "./plan";
|
||||
|
|
@ -17,20 +15,22 @@ import {
|
|||
metaDetail,
|
||||
statusQualifier,
|
||||
} from "./tool-format";
|
||||
export { activeAgentRunSummary, agentDisplayItem } from "./agent";
|
||||
export { displayBlocksForItems } from "./blocks";
|
||||
export { normalizeProposedPlanMarkdown } from "./plan";
|
||||
export { classifyExecutionState, executionState, executionStateLabel } from "./state";
|
||||
export { createAutoReviewResultItem, createReviewResultItem } from "./review";
|
||||
export {
|
||||
appendAssistantDelta,
|
||||
appendItemOutput,
|
||||
appendItemText,
|
||||
appendPlanDelta,
|
||||
appendToolOutput,
|
||||
completeReasoningItems,
|
||||
upsertDisplayItem,
|
||||
} from "./stream-updates";
|
||||
|
||||
type UserMessageItem = Extract<ThreadItem, { type: "userMessage" }>;
|
||||
type AgentMessageItem = Extract<ThreadItem, { type: "agentMessage" }>;
|
||||
type PlanItem = Extract<ThreadItem, { type: "plan" }>;
|
||||
type HookPromptItem = Extract<ThreadItem, { type: "hookPrompt" }>;
|
||||
type ReasoningItem = Extract<ThreadItem, { type: "reasoning" }>;
|
||||
type CommandExecutionItem = Extract<ThreadItem, { type: "commandExecution" }>;
|
||||
type CommandAction = CommandExecutionItem["commandActions"][number];
|
||||
type FileChangeItem = Extract<ThreadItem, { type: "fileChange" }>;
|
||||
type McpToolCallItem = Extract<ThreadItem, { type: "mcpToolCall" }>;
|
||||
type DynamicToolCallItem = Extract<ThreadItem, { type: "dynamicToolCall" }>;
|
||||
type WebSearchItem = Extract<ThreadItem, { type: "webSearch" }>;
|
||||
type ImageViewItem = Extract<ThreadItem, { type: "imageView" }>;
|
||||
type ImageGenerationItem = Extract<ThreadItem, { type: "imageGeneration" }>;
|
||||
type ReviewModeItem = Extract<ThreadItem, { type: "enteredReviewMode" }> | Extract<ThreadItem, { type: "exitedReviewMode" }>;
|
||||
type ContextCompactionItem = Extract<ThreadItem, { type: "contextCompaction" }>;
|
||||
|
||||
export function displayItemsFromTurns(turns: Turn[]): DisplayItem[] {
|
||||
const sortedTurns = [...turns].sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
|
||||
|
|
@ -47,209 +47,225 @@ export function displayItemsFromTurns(turns: Turn[]): DisplayItem[] {
|
|||
export function displayItemFromThreadItem(item: ThreadItem, turnId?: string): DisplayItem | null {
|
||||
if (shouldSuppressThreadItem(item)) return null;
|
||||
|
||||
if (item.type === "userMessage") {
|
||||
const text = inputToText(item.content);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "user",
|
||||
text,
|
||||
copyText: text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
};
|
||||
switch (item.type) {
|
||||
case "userMessage":
|
||||
return userMessageDisplayItem(item, turnId);
|
||||
case "agentMessage":
|
||||
return agentMessageDisplayItem(item, turnId);
|
||||
case "commandExecution":
|
||||
return commandDisplayItem(item, turnId);
|
||||
case "fileChange":
|
||||
return fileChangeDisplayItem(item, turnId);
|
||||
case "plan":
|
||||
return planDisplayItem(item, turnId);
|
||||
case "hookPrompt":
|
||||
return hookPromptDisplayItem(item, turnId);
|
||||
case "reasoning":
|
||||
return reasoningDisplayItem(item, turnId);
|
||||
case "mcpToolCall":
|
||||
return mcpToolCallDisplayItem(item, turnId);
|
||||
case "dynamicToolCall":
|
||||
return dynamicToolCallDisplayItem(item, turnId);
|
||||
case "collabAgentToolCall":
|
||||
return agentDisplayItem(item, turnId);
|
||||
case "webSearch":
|
||||
return webSearchDisplayItem(item, turnId);
|
||||
case "imageView":
|
||||
return imageViewDisplayItem(item, turnId);
|
||||
case "imageGeneration":
|
||||
return imageGenerationDisplayItem(item, turnId);
|
||||
case "enteredReviewMode":
|
||||
case "exitedReviewMode":
|
||||
return reviewModeDisplayItem(item, turnId);
|
||||
case "contextCompaction":
|
||||
return contextCompactionDisplayItem(item, turnId);
|
||||
default:
|
||||
return assertNever(item);
|
||||
}
|
||||
|
||||
if (item.type === "agentMessage") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
text: item.text,
|
||||
copyText: item.text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "commandExecution") {
|
||||
return commandDisplayItem(item, turnId);
|
||||
}
|
||||
|
||||
if (item.type === "fileChange") {
|
||||
return fileChangeDisplayItem(item, turnId);
|
||||
}
|
||||
|
||||
if (item.type === "plan") {
|
||||
const text = normalizeProposedPlanMarkdown(item.text);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
text,
|
||||
copyText: text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
proposedPlan: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "hookPrompt") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "hook",
|
||||
role: "tool",
|
||||
text: item.fragments.map((fragment) => fragment.text).join("\n\n") || "Hook prompt",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "reasoning") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "reasoning",
|
||||
role: "tool",
|
||||
text: reasoningText(item),
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "mcpToolCall") {
|
||||
const name = `${item.server}.${item.tool}`;
|
||||
const target = jsonTargetLabel(item.arguments);
|
||||
const failure = item.error?.message ? truncate(item.error.message, 96) : failedStatusLabel(item.status);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
["Result JSON", item.result],
|
||||
["Error JSON", item.error],
|
||||
]),
|
||||
output: "",
|
||||
state: classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "dynamicToolCall") {
|
||||
const name = `${item.namespace ? `${item.namespace}.` : ""}${item.tool}`;
|
||||
const target = jsonTargetLabel(item.arguments);
|
||||
const failure = item.success === false ? "failed" : failedStatusLabel(item.status);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
["Result JSON", item.contentItems],
|
||||
]),
|
||||
output: "",
|
||||
state: item.success === false ? "failed" : classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "collabAgentToolCall") {
|
||||
return agentDisplayItem(item, turnId);
|
||||
}
|
||||
|
||||
if (item.type === "webSearch") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: webSearchSummary(item),
|
||||
toolLabel: "web search",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
details: webSearchDetails(item),
|
||||
output: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "imageView") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, item.path),
|
||||
toolLabel: "imageView",
|
||||
summaryPath: true,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "imageGeneration") {
|
||||
const target = item.savedPath ?? item.result ?? item.revisedPrompt ?? null;
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failedStatusLabel(item.status))),
|
||||
toolLabel: "imageGeneration",
|
||||
summaryPath: Boolean(item.savedPath),
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: [
|
||||
...bodyDetail("Saved path", item.savedPath),
|
||||
...bodyDetail("Revised prompt", item.revisedPrompt),
|
||||
...bodyDetail("Result", item.result),
|
||||
],
|
||||
output: "",
|
||||
state: classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "enteredReviewMode" || item.type === "exitedReviewMode") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: item.type === "enteredReviewMode" ? "Entered review mode" : "Exited review mode",
|
||||
toolLabel: item.type,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
output: item.review,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "contextCompaction") {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: "Context compaction",
|
||||
toolLabel: "contextCompaction",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
type CommandExecutionItem = Extract<ThreadItem, { type: "commandExecution" }>;
|
||||
type CommandAction = CommandExecutionItem["commandActions"][number];
|
||||
type FileChangeItem = Extract<ThreadItem, { type: "fileChange" }>;
|
||||
type ReasoningItem = Extract<ThreadItem, { type: "reasoning" }>;
|
||||
type WebSearchItem = Extract<ThreadItem, { type: "webSearch" }>;
|
||||
function userMessageDisplayItem(item: UserMessageItem, turnId?: string): DisplayItem {
|
||||
const text = inputToText(item.content);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "user",
|
||||
text,
|
||||
copyText: text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
};
|
||||
}
|
||||
|
||||
function agentMessageDisplayItem(item: AgentMessageItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
text: item.text,
|
||||
copyText: item.text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
};
|
||||
}
|
||||
|
||||
function planDisplayItem(item: PlanItem, turnId?: string): DisplayItem {
|
||||
const text = normalizeProposedPlanMarkdown(item.text);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
text,
|
||||
copyText: text,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
markdown: true,
|
||||
proposedPlan: true,
|
||||
};
|
||||
}
|
||||
|
||||
function hookPromptDisplayItem(item: HookPromptItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "hook",
|
||||
role: "tool",
|
||||
text: item.fragments.map((fragment) => fragment.text).join("\n\n") || "Hook prompt",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
function reasoningDisplayItem(item: ReasoningItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "reasoning",
|
||||
role: "tool",
|
||||
text: reasoningText(item),
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
function mcpToolCallDisplayItem(item: McpToolCallItem, turnId?: string): DisplayItem {
|
||||
const name = `${item.server}.${item.tool}`;
|
||||
const target = jsonTargetLabel(item.arguments);
|
||||
const failure = item.error?.message ? truncate(item.error.message, 96) : failedStatusLabel(item.status);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
["Result JSON", item.result],
|
||||
["Error JSON", item.error],
|
||||
]),
|
||||
output: "",
|
||||
state: classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
function dynamicToolCallDisplayItem(item: DynamicToolCallItem, turnId?: string): DisplayItem {
|
||||
const name = `${item.namespace ? `${item.namespace}.` : ""}${item.tool}`;
|
||||
const target = jsonTargetLabel(item.arguments);
|
||||
const failure = item.success === false ? "failed" : failedStatusLabel(item.status);
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
["Result JSON", item.contentItems],
|
||||
]),
|
||||
output: "",
|
||||
state: item.success === false ? "failed" : classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
function webSearchDisplayItem(item: WebSearchItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: webSearchSummary(item),
|
||||
toolLabel: "web search",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
details: webSearchDetails(item),
|
||||
output: "",
|
||||
};
|
||||
}
|
||||
|
||||
function imageViewDisplayItem(item: ImageViewItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, item.path),
|
||||
toolLabel: "imageView",
|
||||
summaryPath: true,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
function imageGenerationDisplayItem(item: ImageGenerationItem, turnId?: string): DisplayItem {
|
||||
const target = item.savedPath ?? item.result ?? item.revisedPrompt ?? null;
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: compactToolSummary(null, target, statusQualifier(item.status, failedStatusLabel(item.status))),
|
||||
toolLabel: "imageGeneration",
|
||||
summaryPath: Boolean(item.savedPath),
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
status: item.status,
|
||||
details: [
|
||||
...bodyDetail("Saved path", item.savedPath),
|
||||
...bodyDetail("Revised prompt", item.revisedPrompt),
|
||||
...bodyDetail("Result", item.result),
|
||||
],
|
||||
output: "",
|
||||
state: classifyExecutionState({ status: item.status }),
|
||||
};
|
||||
}
|
||||
|
||||
function reviewModeDisplayItem(item: ReviewModeItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: item.type === "enteredReviewMode" ? "Entered review mode" : "Exited review mode",
|
||||
toolLabel: item.type,
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
output: item.review,
|
||||
};
|
||||
}
|
||||
|
||||
function contextCompactionDisplayItem(item: ContextCompactionItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: "Context compaction",
|
||||
toolLabel: "contextCompaction",
|
||||
turnId,
|
||||
itemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
function reasoningText(item: ReasoningItem): string {
|
||||
return [...item.summary, ...item.content]
|
||||
|
|
@ -430,24 +446,6 @@ export function fileChangeDisplayItem(item: FileChangeItem, turnId?: string): Di
|
|||
};
|
||||
}
|
||||
|
||||
export function planProgressDisplayItem(turnId: string, explanation: string | null, plan: TurnPlanStep[]): DisplayItem {
|
||||
const lines = plan.map((step) => `${taskStatusMarker(step.status)} ${step.step}`);
|
||||
const body = [explanation?.trim(), ...lines].filter((line): line is string => Boolean(line && line.length > 0)).join("\n");
|
||||
const status = plan.some((step) => step.status === "inProgress" || step.status === "pending") ? "inProgress" : "completed";
|
||||
return {
|
||||
id: `plan-progress-${turnId}`,
|
||||
kind: "taskProgress",
|
||||
role: "tool",
|
||||
text: body || "Plan updated",
|
||||
turnId,
|
||||
itemId: `plan-progress-${turnId}`,
|
||||
explanation: explanation?.trim() || null,
|
||||
steps: plan.map((step) => ({ step: step.step, status: step.status })),
|
||||
status,
|
||||
state: classifyExecutionState({ status }),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeFileChanges(changes: unknown[]): DisplayFileChange[] {
|
||||
return changes.flatMap((change) => {
|
||||
if (!change || typeof change !== "object") return [];
|
||||
|
|
@ -474,11 +472,6 @@ export function pathRelativeToWorkspace(path: string, workspaceRoot?: string | n
|
|||
return pathRelativeToRoot(path, workspaceRoot);
|
||||
}
|
||||
|
||||
export function createSystemItem(text: string): DisplayItem {
|
||||
return {
|
||||
id: `system-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
kind: "system",
|
||||
role: "system",
|
||||
text,
|
||||
};
|
||||
function assertNever(_item: never): null {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { executionState } from "./model";
|
||||
import { pathRelativeToRoot } from "./paths";
|
||||
import { executionState } from "./state";
|
||||
import type {
|
||||
ApprovalResultDisplayItem,
|
||||
CommandDisplayItem,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { approvalResponse, type ApprovalAction, type PendingApproval } from "../approvals/model";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../display/model";
|
||||
import { planProgressDisplayItem } from "../display/plan";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../display/review";
|
||||
import {
|
||||
appendAssistantDelta,
|
||||
appendItemOutput,
|
||||
|
|
@ -9,15 +10,14 @@ import {
|
|||
completeReasoningItems,
|
||||
upsertDisplayItem,
|
||||
} from "../display/stream-updates";
|
||||
import { createSystemItem } from "../display/system";
|
||||
import {
|
||||
createSystemItem,
|
||||
displayItemFromThreadItem,
|
||||
displayItemsFromTurns,
|
||||
normalizeFileChanges,
|
||||
planProgressDisplayItem,
|
||||
shouldSuppressLifecycleItem,
|
||||
shouldSuppressThreadItem,
|
||||
} from "../display/model";
|
||||
} from "../display/thread-items";
|
||||
import type { DisplayItem, DisplayKind, MessageDisplayItem } from "../display/types";
|
||||
import type { RequestId } from "../generated/app-server/RequestId";
|
||||
import type { ServerNotification } from "../generated/app-server/ServerNotification";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
parseReasoningEffortOverride,
|
||||
reasoningEffortOverrideMessage,
|
||||
} from "../runtime/settings";
|
||||
export { slashCommandHelpLines, type SlashCommandName } from "../composer/slash-commands";
|
||||
|
||||
export interface SlashCommandExecutionContext {
|
||||
activeThreadId: string | null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { AppServerClient } from "../app-server/client";
|
||||
import { displayItemsFromTurns } from "../display/model";
|
||||
import { displayItemsFromTurns } from "../display/thread-items";
|
||||
import type { PanelState } from "./state";
|
||||
|
||||
export interface ThreadHistoryLoaderHost {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import type { AppServerClient } from "../app-server/client";
|
|||
import { ConnectionManager, StaleConnectionError } from "../app-server/connection-manager";
|
||||
import type { ServiceTier } from "../app-server/service-tier";
|
||||
import type { ApprovalAction, PendingApproval } from "../approvals/model";
|
||||
import type { SlashCommandName } from "../composer/slash-commands";
|
||||
import { parseSlashCommand } from "../composer/suggestions";
|
||||
import { VIEW_TYPE_CODEX_PANEL } from "../constants";
|
||||
import { createSystemItem } from "../display/model";
|
||||
import { createSystemItem } from "../display/system";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
|
||||
import {
|
||||
|
|
@ -31,12 +32,12 @@ import {
|
|||
runtimeSummaryLabel,
|
||||
serviceTierLabel,
|
||||
setRuntimeOverride,
|
||||
sortedAvailableModels,
|
||||
supportedReasoningEfforts,
|
||||
type RuntimeSnapshot,
|
||||
} from "../runtime/state";
|
||||
import { sortedAvailableModels } from "../runtime/model";
|
||||
import { compactContextLabel, modelOverrideMessage, reasoningEffortOverrideMessage } from "../runtime/settings";
|
||||
import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult, type SlashCommandName } from "./slash-commands";
|
||||
import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult } from "./slash-commands";
|
||||
import { PanelSessionController } from "./session-controller";
|
||||
import { statusValue, usageLimitStatusLines } from "./status-lines";
|
||||
import { ThreadHistoryLoader } from "./thread-history";
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { parseServiceTier, serviceTierRequestValue, type ServiceTier, type Servi
|
|||
import { defaultCollaborationMode, planCollaborationMode } from "./collaboration-mode";
|
||||
import { findModelByIdOrName, isReasoningEffort, supportedEffortsForModel } from "./model";
|
||||
import { compactModelLabel, compactReasoningEffortLabel } from "./settings";
|
||||
export { sortedAvailableModels } from "./model";
|
||||
|
||||
export type RuntimeOverride<T> = { kind: "default" } | { kind: "set"; value: T } | { kind: "resetPending" };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { displayBlocksForItems } from "../display/blocks";
|
||||
import { executionState } from "../display/model";
|
||||
import { displayItemSignature } from "../display/signature";
|
||||
import { executionState } from "../display/state";
|
||||
import type { DisplayBlock, DisplayDetailSection, DisplayItem } from "../display/types";
|
||||
import { createIconButton, createMetaPair, createRememberedDetails } from "./components";
|
||||
import { shortSignature } from "./dom";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { activeAgentRunSummary, executionState } from "../display/model";
|
||||
import { activeAgentRunSummary } from "../display/agent";
|
||||
import { isReasoningActive } from "../display/signature";
|
||||
import { executionState } from "../display/state";
|
||||
import type { AgentDisplayItem, AgentRunSummary, AgentRunSummaryAgent, DisplayItem, TaskProgressDisplayItem } from "../display/types";
|
||||
import { agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel, taskStatusMarker } from "../display/labels";
|
||||
import type { MessageStreamContext } from "./message-stream";
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { activeAgentRunSummary } from "../src/display/agent";
|
||||
import { displayBlocksForItems } from "../src/display/blocks";
|
||||
import {
|
||||
appendAssistantDelta,
|
||||
activeAgentRunSummary,
|
||||
appendItemOutput,
|
||||
appendItemText,
|
||||
appendPlanDelta,
|
||||
appendToolOutput,
|
||||
createAutoReviewResultItem,
|
||||
createReviewResultItem,
|
||||
displayBlocksForItems,
|
||||
displayItemFromThreadItem,
|
||||
displayItemsFromTurns,
|
||||
executionState,
|
||||
normalizeProposedPlanMarkdown,
|
||||
planProgressDisplayItem,
|
||||
upsertDisplayItem,
|
||||
} from "../src/display/model";
|
||||
} from "../src/display/stream-updates";
|
||||
import { normalizeProposedPlanMarkdown, planProgressDisplayItem } from "../src/display/plan";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../src/display/review";
|
||||
import { executionState } from "../src/display/state";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns } from "../src/display/thread-items";
|
||||
import type { DisplayItem } from "../src/display/types";
|
||||
import type { ThreadItem } from "../src/generated/app-server/v2/ThreadItem";
|
||||
import type { Turn } from "../src/generated/app-server/v2/Turn";
|
||||
|
|
@ -76,6 +73,11 @@ describe("display model", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("suppresses legacy output thread items", () => {
|
||||
expect(displayItemFromThreadItem({ type: "outputMessage", id: "out-1" } as unknown as ThreadItem)).toBeNull();
|
||||
expect(displayItemFromThreadItem({ type: "toolOutputMessage", id: "tool-out-1" } as unknown as ThreadItem)).toBeNull();
|
||||
});
|
||||
|
||||
it("preserves reasoning text", () => {
|
||||
const item: ThreadItem = { type: "reasoning", id: "r1", summary: ["summary"], content: ["detail"] };
|
||||
expect(displayItemFromThreadItem(item)?.text).toBe("summary\n\ndetail");
|
||||
|
|
@ -362,7 +364,7 @@ describe("display model", () => {
|
|||
source: "agent",
|
||||
status: "completed",
|
||||
commandActions: [{ type: "listFiles", command: "rg", path: "src/display" }],
|
||||
aggregatedOutput: "src/display/model.ts",
|
||||
aggregatedOutput: "src/display/thread-items.ts",
|
||||
exitCode: 0,
|
||||
durationMs: 10,
|
||||
};
|
||||
|
|
@ -558,6 +560,31 @@ describe("display model", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("preserves image generation status, details, and state", () => {
|
||||
const item: ThreadItem = {
|
||||
type: "imageGeneration",
|
||||
id: "image-gen-1",
|
||||
status: "completed",
|
||||
revisedPrompt: "A precise UI mockup.",
|
||||
result: "image result",
|
||||
savedPath: "/vault/project/assets/generated.png",
|
||||
};
|
||||
|
||||
expect(displayItemFromThreadItem(item, "t1")).toMatchObject({
|
||||
kind: "tool",
|
||||
text: "/vault/project/assets/generated.png",
|
||||
toolLabel: "imageGeneration",
|
||||
summaryPath: true,
|
||||
status: "completed",
|
||||
details: [
|
||||
{ title: "Saved path", body: "/vault/project/assets/generated.png" },
|
||||
{ title: "Revised prompt", body: "A precise UI mockup." },
|
||||
{ title: "Result", body: "image result" },
|
||||
],
|
||||
state: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses details as the summary when a tool target cannot be extracted", () => {
|
||||
const item: ThreadItem = {
|
||||
type: "dynamicToolCall",
|
||||
|
|
@ -603,6 +630,36 @@ describe("display model", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("preserves review mode items with their review output", () => {
|
||||
const entered: ThreadItem = { type: "enteredReviewMode", id: "review-entered", review: "Review started" };
|
||||
const exited: ThreadItem = { type: "exitedReviewMode", id: "review-exited", review: "Review finished" };
|
||||
|
||||
expect(displayItemFromThreadItem(entered, "t1")).toMatchObject({
|
||||
kind: "tool",
|
||||
text: "Entered review mode",
|
||||
toolLabel: "enteredReviewMode",
|
||||
output: "Review started",
|
||||
});
|
||||
expect(displayItemFromThreadItem(exited, "t1")).toMatchObject({
|
||||
kind: "tool",
|
||||
text: "Exited review mode",
|
||||
toolLabel: "exitedReviewMode",
|
||||
output: "Review finished",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves context compaction items as short tool items", () => {
|
||||
const item: ThreadItem = { type: "contextCompaction", id: "compact-1" };
|
||||
|
||||
expect(displayItemFromThreadItem(item, "t1")).toMatchObject({
|
||||
kind: "tool",
|
||||
text: "Context compaction",
|
||||
toolLabel: "contextCompaction",
|
||||
turnId: "t1",
|
||||
itemId: "compact-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("structures automatic approval review summary messages", () => {
|
||||
expect(
|
||||
createReviewResultItem(
|
||||
|
|
@ -637,7 +694,7 @@ describe("display model", () => {
|
|||
targetItemId: "patch-1",
|
||||
decisionSource: "agent",
|
||||
review: { status: "approved", riskLevel: "low", userAuthorization: "medium", rationale: "Allowed by policy." },
|
||||
action: { type: "applyPatch", cwd: "/vault", files: ["/vault/src/display/model.ts", "/vault/tests/display-model.test.ts"] },
|
||||
action: { type: "applyPatch", cwd: "/vault", files: ["/vault/src/display/thread-items.ts", "/vault/tests/display-model.test.ts"] },
|
||||
}),
|
||||
).toMatchObject({
|
||||
kind: "reviewResult",
|
||||
|
|
@ -648,7 +705,7 @@ describe("display model", () => {
|
|||
rows: expect.arrayContaining([
|
||||
{ key: "action", value: "apply patch" },
|
||||
{ key: "cwd", value: "/vault" },
|
||||
{ key: "files", value: "src/display/model.ts\ntests/display-model.test.ts" },
|
||||
{ key: "files", value: "src/display/thread-items.ts\ntests/display-model.test.ts" },
|
||||
]),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { slashCommandHelpLines } from "../src/composer/slash-commands";
|
||||
import type { Thread } from "../src/generated/app-server/v2/Thread";
|
||||
import { executeSlashCommand, slashCommandHelpLines, type SlashCommandExecutionContext } from "../src/panel/slash-commands";
|
||||
import { executeSlashCommand, type SlashCommandExecutionContext } from "../src/panel/slash-commands";
|
||||
|
||||
function context(overrides: Partial<SlashCommandExecutionContext> = {}): SlashCommandExecutionContext {
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in a new issue