mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Organize chat display and panel view-model modules
This commit is contained in:
parent
b98f8e6c1f
commit
c7ecbf65bc
47 changed files with 534 additions and 537 deletions
|
|
@ -110,7 +110,7 @@ const chatPreactDomBridgeFiles = [
|
|||
"src/features/chat/ui/goal-banner.tsx",
|
||||
"src/features/chat/ui/shell.tsx",
|
||||
"src/features/chat/ui/tool-result.tsx",
|
||||
"src/features/chat/ui/turn-diff.tsx",
|
||||
"src/features/chat/turn-diff/render.tsx",
|
||||
];
|
||||
const chatImperativeDomBridgeFiles = [...chatExternalDomBridgeFiles, ...chatPreactDomBridgeFiles];
|
||||
const nonChatImperativeDomBridgeFiles = [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Thread } from "../../domain/threads/model";
|
||||
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
|
||||
import type { CodexPanelSettings } from "../../settings/model";
|
||||
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
||||
import type { ChatTurnDiffViewState } from "./turn-diff/model";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settings: CodexPanelSettings;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { CodexInput } from "../../../../app-server/request-input";
|
|||
import { isComposerSendKey, type SendShortcut } from "../../../../shared/ui/keyboard";
|
||||
import { textareaCursorAtVisualBoundary } from "../../../../shared/ui/textarea-caret";
|
||||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "../../state/reducer";
|
||||
import type { ComposerMetaViewModel } from "../../panel/view-model/types";
|
||||
import type { ComposerMetaViewModel } from "../../panel/view-model/composer";
|
||||
import { composerShellNode, syncComposerHeight, type ComposerCallbacks } from "../../ui/composer";
|
||||
import { composerBoundaryScrollDirection, type ComposerBoundaryScrollAction } from "./boundary-scroll";
|
||||
import { noteCandidates as appNoteCandidates, resolveWikiLinkMention as resolveAppWikiLinkMention } from "./obsidian-context";
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import type { RuntimeSnapshot } from "../../runtime/model";
|
|||
import { ChatMessageRenderer } from "../../ui/message-stream/renderer";
|
||||
import type { DisplayDetailSection } from "../../display/types";
|
||||
import type { ChatMessageScrollIntentController } from "../../panel/message-scroll-intent-controller";
|
||||
import type { ChatTurnDiffViewState } from "../../ui/turn-diff";
|
||||
import type { ComposerMetaViewModel } from "../../panel/view-model/types";
|
||||
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
||||
import type { ComposerMetaViewModel } from "../../panel/view-model/composer";
|
||||
import type { CodexPanelSettings } from "../../../../settings/model";
|
||||
|
||||
interface ConversationSurfaceControllerGroupPorts {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,28 @@
|
|||
import type { AgentDisplayItem, AgentRunSummary, AgentRunSummaryAgent, AgentStateDisplay, DisplayItem, ExecutionState } from "./types";
|
||||
import { definedProp } from "../../../utils";
|
||||
import { agentActivitySummaryLabel, agentMessagePreview } from "./labels";
|
||||
import { collabAgentStateExecutionState, collabAgentToolCallExecutionState } from "./state";
|
||||
import { definedProp, truncate } from "../../../utils";
|
||||
|
||||
const ACTIVE_AGENT_PREVIEW_LIMIT = 96;
|
||||
type AgentRunState = "running" | "completed" | "failed";
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
const AGENT_STATES = {
|
||||
pendingInit: "running",
|
||||
running: "running",
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
shutdown: "completed",
|
||||
interrupted: "failed",
|
||||
errored: "failed",
|
||||
notFound: "failed",
|
||||
failed: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const STANDARD_TOOL_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
interface DisplayCollabAgentToolCall {
|
||||
id: string;
|
||||
|
|
@ -46,6 +64,24 @@ export function agentDisplayItem(item: DisplayCollabAgentToolCall, turnId?: stri
|
|||
};
|
||||
}
|
||||
|
||||
export function agentActivitySummaryLabel(tool: string): string {
|
||||
if (tool === "spawnAgent") return "Spawn agent";
|
||||
if (tool === "sendInput") return "Send input to agent";
|
||||
if (tool === "resumeAgent") return "Resume agent";
|
||||
if (tool === "wait") return "Wait for agent";
|
||||
if (tool === "closeAgent") return "Close agent";
|
||||
return `Agent ${tool}`;
|
||||
}
|
||||
|
||||
export function agentActivityMetaLabel(tool: string): string {
|
||||
if (tool === "spawnAgent") return "spawn";
|
||||
if (tool === "sendInput") return "send input";
|
||||
if (tool === "resumeAgent") return "resume";
|
||||
if (tool === "wait") return "wait";
|
||||
if (tool === "closeAgent") return "close";
|
||||
return tool;
|
||||
}
|
||||
|
||||
export function activeAgentRunSummary(items: readonly DisplayItem[], activeTurnId: string | null): AgentRunSummary | null {
|
||||
if (!activeTurnId) return null;
|
||||
|
||||
|
|
@ -86,6 +122,24 @@ export function activeAgentRunSummary(items: readonly DisplayItem[], activeTurnI
|
|||
return summary;
|
||||
}
|
||||
|
||||
export function agentRunSummaryLabel(summary: AgentRunSummary): string {
|
||||
const parts: string[] = [];
|
||||
if (summary.failed > 0) parts.push(`${String(summary.failed)} failed`);
|
||||
if (summary.running > 0) parts.push(`${String(summary.running)} running`);
|
||||
if (summary.completed > 0) parts.push(`${String(summary.completed)} done`);
|
||||
return `Agents ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
export function agentMessagePreview(message: string | null, maxLength: number): string | null {
|
||||
if (!message) return null;
|
||||
const firstLine = message
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find((line) => line.length > 0);
|
||||
if (!firstLine) return null;
|
||||
return truncate(firstLine.replace(/\s+/g, " "), maxLength);
|
||||
}
|
||||
|
||||
function agentStatesDisplay(states: DisplayCollabAgentToolCall["agentsStates"]): AgentStateDisplay[] {
|
||||
return Object.entries(states)
|
||||
.map(([threadId, state]) => ({
|
||||
|
|
@ -112,3 +166,15 @@ function collabAgentExecutionState(tool: string, status: string, receiverThreadI
|
|||
function agentRunState(status: string): AgentRunState {
|
||||
return collabAgentStateExecutionState(status) ?? "running";
|
||||
}
|
||||
|
||||
export function collabAgentStateExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, AGENT_STATES);
|
||||
}
|
||||
|
||||
function collabAgentToolCallExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, STANDARD_TOOL_STATES);
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { DisplayBlock, DisplayItem, DisplayKind } from "./types";
|
||||
import { isCompletedTurnOutcomeMessage } from "./turn-outcome-message";
|
||||
import { pathRelativeToRoot } from "./paths";
|
||||
import { executionState } from "./state";
|
||||
|
||||
const STEERING_ACTIVITY_LABEL = "steer";
|
||||
const STEERING_ACTIVITY_KIND = "userSteered";
|
||||
|
|
@ -62,7 +61,7 @@ export function displayBlocksForItems(
|
|||
}
|
||||
|
||||
function shouldShowDisplayItem(item: DisplayItem): boolean {
|
||||
return item.kind !== "reasoning" || executionState(item) !== "completed" || item.text.trim().length > 0;
|
||||
return item.kind !== "reasoning" || item.executionState !== "completed" || item.text.trim().length > 0;
|
||||
}
|
||||
|
||||
function isSteeringUserMessage(item: DisplayItem, seenUserMessagesByTurn: Map<string, number>): boolean {
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { truncate } from "../../../utils";
|
||||
import type { AgentRunSummary } from "./types";
|
||||
|
||||
export type TaskStepStatus = "pending" | "inProgress" | "completed";
|
||||
|
||||
export function taskStatusMarker(status: TaskStepStatus): string {
|
||||
if (status === "completed") return "[x]";
|
||||
if (status === "inProgress") return "[>]";
|
||||
return "[ ]";
|
||||
}
|
||||
|
||||
export function agentActivitySummaryLabel(tool: string): string {
|
||||
if (tool === "spawnAgent") return "Spawn agent";
|
||||
if (tool === "sendInput") return "Send input to agent";
|
||||
if (tool === "resumeAgent") return "Resume agent";
|
||||
if (tool === "wait") return "Wait for agent";
|
||||
if (tool === "closeAgent") return "Close agent";
|
||||
return `Agent ${tool}`;
|
||||
}
|
||||
|
||||
export function agentActivityMetaLabel(tool: string): string {
|
||||
if (tool === "spawnAgent") return "spawn";
|
||||
if (tool === "sendInput") return "send input";
|
||||
if (tool === "resumeAgent") return "resume";
|
||||
if (tool === "wait") return "wait";
|
||||
if (tool === "closeAgent") return "close";
|
||||
return tool;
|
||||
}
|
||||
|
||||
export function agentRunSummaryLabel(summary: AgentRunSummary): string {
|
||||
const parts: string[] = [];
|
||||
if (summary.failed > 0) parts.push(`${String(summary.failed)} failed`);
|
||||
if (summary.running > 0) parts.push(`${String(summary.running)} running`);
|
||||
if (summary.completed > 0) parts.push(`${String(summary.completed)} done`);
|
||||
return `Agents ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
export function agentMessagePreview(message: string | null, maxLength: number): string | null {
|
||||
if (!message) return null;
|
||||
const firstLine = message
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find((line) => line.length > 0);
|
||||
if (!firstLine) return null;
|
||||
return truncate(firstLine.replace(/\s+/g, " "), maxLength);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { jsonPreview } from "../../../utils";
|
||||
|
||||
export interface DetailRow {
|
||||
interface DetailRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ export function permissionRows(permissions: DisplayPermissionProfile): DetailRow
|
|||
return rows;
|
||||
}
|
||||
|
||||
export function addOptional(rows: DetailRow[], key: string, value: unknown): void {
|
||||
function addOptional(rows: DetailRow[], key: string, value: unknown): void {
|
||||
if (value === null || value === undefined) return;
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
rows.push({ key, value: stringValue(value) });
|
||||
|
|
@ -65,10 +65,6 @@ function stringValue(value: unknown, fallback = ""): string {
|
|||
return jsonPreview(value);
|
||||
}
|
||||
|
||||
export function nonEmptyString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function fileSystemPathLabel(path: DisplayFileSystemPath): string {
|
||||
if (path.type === "path") return path.path;
|
||||
if (path.type === "glob_pattern") return path.pattern;
|
||||
|
|
@ -1,12 +1,31 @@
|
|||
import type { DisplayItem } from "./types";
|
||||
import { taskStatusMarker, type TaskStepStatus } from "./labels";
|
||||
import { taskProgressExecutionState } from "./state";
|
||||
import type { DisplayItem, ExecutionState } from "./types";
|
||||
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
const TASK_STATES = {
|
||||
pending: "running",
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
export type TaskStepStatus = "pending" | "inProgress" | "completed";
|
||||
|
||||
export interface TaskPlanStep {
|
||||
step: string;
|
||||
status: TaskStepStatus;
|
||||
}
|
||||
|
||||
export function taskStatusMarker(status: TaskStepStatus): string {
|
||||
if (status === "completed") return "[x]";
|
||||
if (status === "inProgress") return "[>]";
|
||||
return "[ ]";
|
||||
}
|
||||
|
||||
export function taskProgressExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, TASK_STATES);
|
||||
}
|
||||
|
||||
export function normalizeProposedPlanMarkdown(text: string): string {
|
||||
return text
|
||||
.replace(/^\s*<proposed_plan>\s*\n?/i, "")
|
||||
|
|
@ -32,3 +51,7 @@ export function planProgressDisplayItem(turnId: string, explanation: string | nu
|
|||
executionState: taskProgressExecutionState(status),
|
||||
};
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import { permissionRows } from "./permission-details";
|
||||
import type { DisplayItem } from "./types";
|
||||
import { permissionRows } from "./permission-rows";
|
||||
import type { DisplayItem, ExecutionState } from "./types";
|
||||
import { pathsRelativeToRoot } from "./paths";
|
||||
import { autoReviewExecutionState } from "./state";
|
||||
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
const AUTO_REVIEW_STATES = {
|
||||
inProgress: "running",
|
||||
approved: "completed",
|
||||
denied: "failed",
|
||||
timedOut: "failed",
|
||||
aborted: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
type AutoReviewNotification = AutoReviewStartedNotification | AutoReviewCompletedNotification;
|
||||
|
||||
|
|
@ -177,3 +187,11 @@ function autoReviewActionLabel(action: AutoReviewAction): string {
|
|||
if (action.type === "mcpToolCall") return `${action.server}.${action.toolName}`;
|
||||
return action.reason ?? "permission request";
|
||||
}
|
||||
|
||||
export function autoReviewExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, AUTO_REVIEW_STATES);
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { RuntimeConfigSnapshot } from "../../../app-server/runtime-config";
|
|||
import { jsonPreview } from "../../../utils";
|
||||
import { sortedModelMetadata } from "../../../domain/catalog/metadata";
|
||||
import { defaultEffortForModelMetadata } from "../../../domain/catalog/metadata";
|
||||
import type { ChatState } from "../state/reducer";
|
||||
import {
|
||||
currentApprovalsReviewer,
|
||||
currentApprovalPolicy,
|
||||
|
|
@ -12,8 +13,9 @@ import {
|
|||
fastModeLabel,
|
||||
runtimeConfigOrDefault,
|
||||
serviceTierLabel,
|
||||
} from "./effective-settings";
|
||||
import { collaborationModeLabel, pendingRuntimeSettingLabel, type RuntimeSnapshot } from "./model";
|
||||
supportedReasoningEfforts,
|
||||
} from "../runtime/effective-settings";
|
||||
import { collaborationModeLabel, pendingRuntimeSettingLabel, type RuntimeSnapshot } from "../runtime/model";
|
||||
|
||||
export interface ContextSummary {
|
||||
label: string;
|
||||
|
|
@ -43,6 +45,25 @@ export interface RuntimeConfigSection {
|
|||
rows: { key: string; value: string }[];
|
||||
}
|
||||
|
||||
export interface StatusSummaryLinesInput {
|
||||
activeThreadId: ChatState["activeThread"]["id"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
nowMs: number;
|
||||
}
|
||||
|
||||
export interface ModelStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedModel: ChatState["runtime"]["requestedModel"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
collaborationModeLabel: string;
|
||||
}
|
||||
|
||||
export interface EffortStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedReasoningEffort: ChatState["runtime"]["requestedReasoningEffort"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
}
|
||||
|
||||
const CODEX_DEFAULT_LABEL = "(Codex default)";
|
||||
const NOT_REPORTED_LABEL = "(not reported)";
|
||||
|
||||
|
|
@ -86,7 +107,7 @@ export function contextSummary(snapshot: RuntimeSnapshot): ContextSummary | null
|
|||
};
|
||||
}
|
||||
|
||||
export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs = Date.now()): RateLimitSummary | null {
|
||||
export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs: number): RateLimitSummary | null {
|
||||
const rateLimit = snapshot.rateLimit;
|
||||
if (!rateLimit) return null;
|
||||
|
||||
|
|
@ -176,6 +197,38 @@ export function runtimeConfigSections(snapshot: RuntimeSnapshot, vaultPath: stri
|
|||
];
|
||||
}
|
||||
|
||||
export function statusSummaryLines(input: StatusSummaryLinesInput): string[] {
|
||||
const context = contextSummary(input.snapshot);
|
||||
const limit = rateLimitSummary(input.snapshot, input.nowMs);
|
||||
return [
|
||||
"Thread status",
|
||||
`Thread: ${input.activeThreadId ?? "(none)"}`,
|
||||
context ? context.title : "Context: not available",
|
||||
...(limit ? usageLimitStatusLines(limit) : ["Usage limits: not available"]),
|
||||
];
|
||||
}
|
||||
|
||||
export function modelStatusLines(input: ModelStatusLinesInput): string[] {
|
||||
const config = runtimeConfigOrDefault(input.runtimeConfig);
|
||||
return [
|
||||
`Model: ${currentModel(input.snapshot, config) ?? CODEX_DEFAULT_LABEL}`,
|
||||
`Override: ${pendingRuntimeSettingLabel(input.requestedModel)}`,
|
||||
`Provider: ${stringValue(config.modelProvider, CODEX_DEFAULT_LABEL)}`,
|
||||
`Effort: ${currentReasoningEffort(input.snapshot, config) ?? CODEX_DEFAULT_LABEL}`,
|
||||
`Mode: ${input.collaborationModeLabel}`,
|
||||
`Service tier: ${serviceTierLabel(input.snapshot, config)}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function effortStatusLines(input: EffortStatusLinesInput): string[] {
|
||||
const config = runtimeConfigOrDefault(input.runtimeConfig);
|
||||
return [
|
||||
`Effort: ${currentReasoningEffort(input.snapshot, config) ?? CODEX_DEFAULT_LABEL}`,
|
||||
`Override: ${pendingRuntimeSettingLabel(input.requestedReasoningEffort)}`,
|
||||
`Supported: ${supportedReasoningEfforts(input.snapshot, config).join(", ")}`,
|
||||
];
|
||||
}
|
||||
|
||||
function contextUsageTokens(usage: ThreadTokenUsage): number {
|
||||
return usage.last.inputTokens > 0 ? usage.last.inputTokens : usage.last.totalTokens;
|
||||
}
|
||||
|
|
@ -202,6 +255,10 @@ function activePermissionProfileLabel(profile: RuntimeSnapshot["activePermission
|
|||
return profile.extends ? `${profile.id} extends ${profile.extends}` : profile.id;
|
||||
}
|
||||
|
||||
function usageLimitStatusLines(limit: RateLimitSummary): string[] {
|
||||
return ["Usage limits", ...limit.rows.map((row) => `${row.label}: ${row.value}${row.resetLabel ? ` (${row.resetLabel})` : ""}`)];
|
||||
}
|
||||
|
||||
function rateLimitWindowSummary(
|
||||
fallbackLabel: string,
|
||||
window: RateLimitWindow | null,
|
||||
|
|
@ -262,7 +319,7 @@ function formatRateLimitDuration(minutes: number): string {
|
|||
}
|
||||
|
||||
function formatRateLimitReset(resetsAt: number): string {
|
||||
return new Date(resetsAt * 1000).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
|
||||
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(resetsAt * 1000);
|
||||
}
|
||||
|
||||
function formatRateLimitRemaining(resetsAt: number, nowMs: number): string {
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
import type { DisplayItem, ExecutionState } from "./types";
|
||||
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
const COMMAND_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
declined: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const PATCH_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
declined: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const STANDARD_TOOL_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const TASK_STATES = {
|
||||
pending: "running",
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const AUTO_REVIEW_STATES = {
|
||||
inProgress: "running",
|
||||
approved: "completed",
|
||||
denied: "failed",
|
||||
timedOut: "failed",
|
||||
aborted: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const AGENT_STATES = {
|
||||
pendingInit: "running",
|
||||
running: "running",
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
shutdown: "completed",
|
||||
interrupted: "failed",
|
||||
errored: "failed",
|
||||
notFound: "failed",
|
||||
failed: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
export function executionState(item: DisplayItem): ExecutionState {
|
||||
return item.executionState ?? null;
|
||||
}
|
||||
|
||||
export function commandExecutionState(status: string, exitCode?: number): ExecutionState {
|
||||
if (typeof exitCode === "number" && exitCode !== 0) return "failed";
|
||||
const state = executionStateFromStatus(status, COMMAND_STATES);
|
||||
if (state) return state;
|
||||
if (typeof exitCode === "number") return "completed";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function patchApplyExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, PATCH_STATES);
|
||||
}
|
||||
|
||||
export function mcpToolCallExecutionState(status: string): ExecutionState {
|
||||
return standardToolCallExecutionState(status);
|
||||
}
|
||||
|
||||
export function dynamicToolCallExecutionState(status: string, success?: boolean | null): ExecutionState {
|
||||
if (success === false) return "failed";
|
||||
const state = standardToolCallExecutionState(status);
|
||||
if (state) return state;
|
||||
return success === true ? "completed" : null;
|
||||
}
|
||||
|
||||
export function imageGenerationExecutionState(status: string): ExecutionState {
|
||||
return standardToolCallExecutionState(status);
|
||||
}
|
||||
|
||||
export function taskProgressExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, TASK_STATES);
|
||||
}
|
||||
|
||||
export function autoReviewExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, AUTO_REVIEW_STATES);
|
||||
}
|
||||
|
||||
export function collabAgentToolCallExecutionState(status: string): ExecutionState {
|
||||
return standardToolCallExecutionState(status);
|
||||
}
|
||||
|
||||
export function collabAgentStateExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, AGENT_STATES);
|
||||
}
|
||||
|
||||
function standardToolCallExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, STANDARD_TOOL_STATES);
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { pathRelativeToRoot } from "./paths";
|
||||
import { executionState } from "./state";
|
||||
import { definedProp } from "../../../utils";
|
||||
import type {
|
||||
ApprovalResultDisplayItem,
|
||||
|
|
@ -157,7 +156,7 @@ function toolView(
|
|||
summary,
|
||||
detailsKey,
|
||||
details,
|
||||
state: executionState(item),
|
||||
state: item.executionState ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import type { Thread } from "../../../domain/threads/model";
|
|||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
import type { RuntimeSnapshot } from "../runtime/model";
|
||||
import type { ChatState, ChatStateStore } from "../state/reducer";
|
||||
import type { ChatTurnDiffViewState } from "../ui/turn-diff";
|
||||
import type { ChatTurnDiffViewState } from "../turn-diff/model";
|
||||
import type { ChatMessageScrollIntentController } from "../panel/message-scroll-intent-controller";
|
||||
import type { DisplayDetailSection, DisplayItem } from "../display/types";
|
||||
import type { ChatConnectionWorkTracker, ChatResumeWorkTracker, ChatViewDeferredTasks } from "./lifecycle";
|
||||
import type { ComposerMetaViewModel } from "./view-model/types";
|
||||
import type { ComposerMetaViewModel } from "./view-model/composer";
|
||||
|
||||
export interface ChatControllerCompositionPorts {
|
||||
obsidian: ChatPanelObsidianContext;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { pendingRequestsSignature as requestStateSignature } from "../pending-re
|
|||
import {
|
||||
composerMetaViewModel as buildComposerMetaViewModel,
|
||||
composerPlaceholder as buildComposerPlaceholder,
|
||||
} from "../panel/view-model/composer";
|
||||
import { runtimeComposerChoices } from "../panel/view-model/runtime";
|
||||
import { activeComposerThreadName as buildActiveComposerThreadName } from "../panel/view-model/thread-title";
|
||||
import { toolbarViewModel as buildToolbarViewModel } from "../panel/view-model/toolbar";
|
||||
import type { GoalBannerActions, GoalBannerOptions } from "./goal-banner";
|
||||
import type { ChatPanelComposerPorts, ChatPanelGoalPorts, ChatPanelStatePort, ChatPanelToolbarPorts } from "../panel/ui-ports";
|
||||
import type { ChatPanelShellState } from "./shell";
|
||||
runtimeComposerChoices,
|
||||
} from "./view-model/composer";
|
||||
import { activeComposerThreadName as buildActiveComposerThreadName } from "./view-model/thread-title";
|
||||
import { toolbarViewModel as buildToolbarViewModel } from "./view-model/toolbar";
|
||||
import type { GoalBannerActions, GoalBannerOptions } from "../ui/goal-banner";
|
||||
import type { ChatPanelComposerPorts, ChatPanelGoalPorts, ChatPanelStatePort, ChatPanelToolbarPorts } from "./ui-ports";
|
||||
import type { ChatPanelShellState } from "../ui/shell";
|
||||
|
||||
export function chatPanelToolbarViewModel(ports: ChatPanelToolbarPorts, shellState: ChatPanelShellState) {
|
||||
const latestState = shellState.latestState();
|
||||
|
|
@ -3,8 +3,8 @@ import type { ReasoningEffort } from "../../../domain/catalog/metadata";
|
|||
import type { RuntimeSnapshot } from "../runtime/model";
|
||||
import type { SendShortcut } from "../../../shared/ui/keyboard";
|
||||
import type { ToolbarActions } from "../ui/toolbar";
|
||||
import type { ToolbarThreadRow } from "./view-model/types";
|
||||
import type { RestoredThreadTitleSnapshot } from "./view-model/types";
|
||||
import type { RestoredThreadTitleSnapshot } from "./view-model/thread-title";
|
||||
import type { ToolbarThreadRow } from "./view-model/toolbar";
|
||||
|
||||
interface ChatPanelToolbarState {
|
||||
archiveConfirmId: () => string | null;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,53 @@ import {
|
|||
currentReasoningEffort,
|
||||
fastModeActive,
|
||||
runtimeConfigOrDefault,
|
||||
supportedReasoningEfforts,
|
||||
} from "../../runtime/effective-settings";
|
||||
import { compactReasoningEffortLabel } from "../../conversation/turns/runtime-overrides";
|
||||
import { contextSummary } from "../../runtime/status-summary";
|
||||
import { contextSummary } from "../../display/runtime-status";
|
||||
import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeSnapshot } from "../../runtime/model";
|
||||
import type { ChatState } from "../../state/reducer";
|
||||
import type { ComposerContextMeterCellViewModel, ComposerContextMeterViewModel, ComposerMetaViewModel } from "./types";
|
||||
|
||||
export interface ComposerMetaViewModel {
|
||||
fatal: string | null;
|
||||
context: ComposerContextMeterViewModel;
|
||||
statusSummary: string;
|
||||
model: string;
|
||||
effort: string | null;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
modelChoices?: RuntimeChoice[];
|
||||
effortChoices?: RuntimeChoice[];
|
||||
}
|
||||
|
||||
export interface RuntimeChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterCellViewModel {
|
||||
text: string;
|
||||
placeholder: boolean;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterViewModel {
|
||||
cells: ComposerContextMeterCellViewModel[];
|
||||
percent: string;
|
||||
}
|
||||
|
||||
export interface RuntimeComposerChoicesInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
requestModel: (model: string) => void;
|
||||
requestReasoningEffort: (effort: ReasoningEffort) => void;
|
||||
resetReasoningEffortToConfig: () => void;
|
||||
}
|
||||
|
||||
export function composerPlaceholder(threadName: string | null): string {
|
||||
return threadName ? `Ask Codex to work on “${threadName}”...` : "Ask Codex to work on this task...";
|
||||
|
|
@ -57,6 +98,49 @@ export function composerMetaViewModel(state: ChatState, snapshot: RuntimeSnapsho
|
|||
};
|
||||
}
|
||||
|
||||
export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
||||
modelChoices: RuntimeChoice[];
|
||||
effortChoices: RuntimeChoice[];
|
||||
} {
|
||||
const config = runtimeConfigOrDefault(input.state.connection.runtimeConfig);
|
||||
const activeModel = currentModel(input.snapshot, config);
|
||||
const models = sortedModelMetadata(input.state.connection.availableModels);
|
||||
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => {
|
||||
input.requestModel(model.model);
|
||||
},
|
||||
}));
|
||||
if (models.length === 0) {
|
||||
modelChoices.push({
|
||||
label: "No model list available.",
|
||||
disabled: true,
|
||||
onClick: () => undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const activeEffort = currentReasoningEffort(input.snapshot, config);
|
||||
const effortChoices: RuntimeChoice[] = [
|
||||
{
|
||||
label: "Codex default",
|
||||
selected: activeEffort === null,
|
||||
onClick: () => {
|
||||
input.resetReasoningEffortToConfig();
|
||||
},
|
||||
},
|
||||
...supportedReasoningEfforts(input.snapshot, config).map((effort) => ({
|
||||
label: effort,
|
||||
selected: activeEffort === effort,
|
||||
onClick: () => {
|
||||
input.requestReasoningEffort(effort);
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
return { modelChoices, effortChoices };
|
||||
}
|
||||
|
||||
function composerStatusSummary(input: {
|
||||
context: ComposerContextMeterViewModel;
|
||||
model: string;
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
import {
|
||||
currentModel,
|
||||
currentReasoningEffort,
|
||||
runtimeConfigOrDefault,
|
||||
serviceTierLabel,
|
||||
supportedReasoningEfforts,
|
||||
} from "../../runtime/effective-settings";
|
||||
import { pendingRuntimeSettingLabel } from "../../runtime/model";
|
||||
import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import { contextSummary, rateLimitSummary, type RateLimitSummary } from "../../runtime/status-summary";
|
||||
import type {
|
||||
EffortStatusLinesInput,
|
||||
ModelStatusLinesInput,
|
||||
RuntimeChoice,
|
||||
RuntimeComposerChoicesInput,
|
||||
StatusSummaryLinesInput,
|
||||
} from "./types";
|
||||
|
||||
export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
||||
modelChoices: RuntimeChoice[];
|
||||
effortChoices: RuntimeChoice[];
|
||||
} {
|
||||
const config = runtimeConfigOrDefault(input.state.connection.runtimeConfig);
|
||||
const activeModel = currentModel(input.snapshot, config);
|
||||
const models = sortedModelMetadata(input.state.connection.availableModels);
|
||||
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => {
|
||||
input.requestModel(model.model);
|
||||
},
|
||||
}));
|
||||
if (models.length === 0) {
|
||||
modelChoices.push({
|
||||
label: "No model list available.",
|
||||
disabled: true,
|
||||
onClick: () => undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const activeEffort = currentReasoningEffort(input.snapshot, config);
|
||||
const effortChoices: RuntimeChoice[] = [
|
||||
{
|
||||
label: "Codex default",
|
||||
selected: activeEffort === null,
|
||||
onClick: () => {
|
||||
input.resetReasoningEffortToConfig();
|
||||
},
|
||||
},
|
||||
...supportedReasoningEfforts(input.snapshot, config).map((effort) => ({
|
||||
label: effort,
|
||||
selected: activeEffort === effort,
|
||||
onClick: () => {
|
||||
input.requestReasoningEffort(effort);
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
return { modelChoices, effortChoices };
|
||||
}
|
||||
|
||||
export function statusSummaryLines(input: StatusSummaryLinesInput): string[] {
|
||||
const context = contextSummary(input.snapshot);
|
||||
const limit = rateLimitSummary(input.snapshot);
|
||||
return [
|
||||
"Thread status",
|
||||
`Thread: ${input.activeThreadId ?? "(none)"}`,
|
||||
context ? context.title : "Context: not available",
|
||||
...(limit ? usageLimitStatusLines(limit) : ["Usage limits: not available"]),
|
||||
];
|
||||
}
|
||||
|
||||
export function modelStatusLines(input: ModelStatusLinesInput): string[] {
|
||||
const config = runtimeConfigOrDefault(input.runtimeConfig);
|
||||
return [
|
||||
`Model: ${currentModel(input.snapshot, config) ?? "(Codex default)"}`,
|
||||
`Override: ${pendingRuntimeSettingLabel(input.requestedModel)}`,
|
||||
`Provider: ${statusValue(config.modelProvider, "(Codex default)")}`,
|
||||
`Effort: ${currentReasoningEffort(input.snapshot, config) ?? "(Codex default)"}`,
|
||||
`Mode: ${input.collaborationModeLabel}`,
|
||||
`Service tier: ${serviceTierLabel(input.snapshot, config)}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function effortStatusLines(input: EffortStatusLinesInput): string[] {
|
||||
const config = runtimeConfigOrDefault(input.runtimeConfig);
|
||||
return [
|
||||
`Effort: ${currentReasoningEffort(input.snapshot, config) ?? "(Codex default)"}`,
|
||||
`Override: ${pendingRuntimeSettingLabel(input.requestedReasoningEffort)}`,
|
||||
`Supported: ${supportedReasoningEfforts(input.snapshot, config).join(", ")}`,
|
||||
];
|
||||
}
|
||||
|
||||
function statusValue(value: unknown, fallback: string): string {
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
||||
if (value === null || value === undefined) return fallback;
|
||||
return jsonPreview(value, fallback);
|
||||
}
|
||||
|
||||
function usageLimitStatusLines(limit: RateLimitSummary): string[] {
|
||||
return ["Usage limits", ...limit.rows.map((row) => `${row.label}: ${row.value}${row.resetLabel ? ` (${row.resetLabel})` : ""}`)];
|
||||
}
|
||||
|
||||
function jsonPreview(value: unknown, fallback: string): string {
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
import { codexPanelDisplayTitle, explicitThreadName, getThreadTitle } from "../../../../domain/threads/model";
|
||||
import type { ChatState } from "../../state/reducer";
|
||||
import type { RestoredThreadTitleSnapshot } from "./types";
|
||||
|
||||
export interface RestoredThreadTitleSnapshot {
|
||||
threadId: string;
|
||||
title: string | null;
|
||||
explicitName: string | null;
|
||||
}
|
||||
|
||||
export function chatViewDisplayTitle(state: ChatState, restoredThreadTitle: string | null): string {
|
||||
return codexPanelDisplayTitle(state.activeThread.id, state.threadList.listedThreads, restoredThreadTitle);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,69 @@
|
|||
import type { Thread } from "../../../../domain/threads/model";
|
||||
import { getThreadTitle } from "../../../../domain/threads/model";
|
||||
import { runtimeConfigSections, rateLimitSummary } from "../../runtime/status-summary";
|
||||
import { runtimeConfigSections, rateLimitSummary } from "../../display/runtime-status";
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../../display/runtime-status";
|
||||
import { connectionDiagnosticSections } from "../../display/diagnostics";
|
||||
import type { ConnectionDiagnosticsModelInput, ToolbarThreadRow, ToolbarViewModel, ToolbarViewModelInput } from "./types";
|
||||
import type { RuntimeSnapshot } from "../../runtime/model";
|
||||
import type { ChatState } from "../../state/reducer";
|
||||
|
||||
export interface ToolbarThreadRow {
|
||||
title: string;
|
||||
threadId: string;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
canArchive: boolean;
|
||||
archiveConfirm?: { active: boolean; defaultSaveMarkdown: boolean };
|
||||
rename: {
|
||||
draft: string;
|
||||
generating: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface ToolbarDiagnosticRow {
|
||||
label: string;
|
||||
value: string;
|
||||
level?: "normal" | "warning" | "error";
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticSection {
|
||||
title: string;
|
||||
rows: ToolbarDiagnosticRow[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModel {
|
||||
newChatDisabled: boolean;
|
||||
chatActionsOpen: boolean;
|
||||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: RuntimeConfigSection[];
|
||||
openPanel: "history" | "chat-actions" | "status" | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
connectLabel: string;
|
||||
diagnostics: ToolbarDiagnosticSection[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModelInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
connected: boolean;
|
||||
turnBusy: boolean;
|
||||
vaultPath: string;
|
||||
configuredCommand: string;
|
||||
archiveConfirmThreadId: string | null;
|
||||
archiveExportEnabled: boolean;
|
||||
renameState: (threadId: string) => ToolbarThreadRow["rename"];
|
||||
}
|
||||
|
||||
export interface ConnectionDiagnosticsModelInput {
|
||||
state: ChatState;
|
||||
connected: boolean;
|
||||
configuredCommand: string;
|
||||
}
|
||||
|
||||
export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel {
|
||||
const { state, snapshot } = input;
|
||||
const limit = rateLimitSummary(snapshot);
|
||||
const limit = rateLimitSummary(snapshot, Date.now());
|
||||
const historyOpen = state.ui.toolbarPanel === "history";
|
||||
const chatActionsOpen = state.ui.toolbarPanel === "chat-actions";
|
||||
const statusPanelOpen = state.ui.toolbarPanel === "status-panel";
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeSnapshot } from "../../runtime/model";
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../../runtime/status-summary";
|
||||
import type { ChatState } from "../../state/reducer";
|
||||
|
||||
export interface ComposerMetaViewModel {
|
||||
fatal: string | null;
|
||||
context: ComposerContextMeterViewModel;
|
||||
statusSummary: string;
|
||||
model: string;
|
||||
effort: string | null;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
modelChoices?: RuntimeChoice[];
|
||||
effortChoices?: RuntimeChoice[];
|
||||
}
|
||||
|
||||
export interface RuntimeChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterCellViewModel {
|
||||
text: string;
|
||||
placeholder: boolean;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterViewModel {
|
||||
cells: ComposerContextMeterCellViewModel[];
|
||||
percent: string;
|
||||
}
|
||||
|
||||
export interface ToolbarThreadRow {
|
||||
title: string;
|
||||
threadId: string;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
canArchive: boolean;
|
||||
archiveConfirm?: { active: boolean; defaultSaveMarkdown: boolean };
|
||||
rename: {
|
||||
draft: string;
|
||||
generating: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface ToolbarDiagnosticRow {
|
||||
label: string;
|
||||
value: string;
|
||||
level?: "normal" | "warning" | "error";
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticSection {
|
||||
title: string;
|
||||
rows: ToolbarDiagnosticRow[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModel {
|
||||
newChatDisabled: boolean;
|
||||
chatActionsOpen: boolean;
|
||||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: RuntimeConfigSection[];
|
||||
openPanel: "history" | "chat-actions" | "status" | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
connectLabel: string;
|
||||
diagnostics: ToolbarDiagnosticSection[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModelInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
connected: boolean;
|
||||
turnBusy: boolean;
|
||||
vaultPath: string;
|
||||
configuredCommand: string;
|
||||
archiveConfirmThreadId: string | null;
|
||||
archiveExportEnabled: boolean;
|
||||
renameState: (threadId: string) => ToolbarThreadRow["rename"];
|
||||
}
|
||||
|
||||
export interface ConnectionDiagnosticsModelInput {
|
||||
state: ChatState;
|
||||
connected: boolean;
|
||||
configuredCommand: string;
|
||||
}
|
||||
|
||||
export interface RuntimeComposerChoicesInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
requestModel: (model: string) => void;
|
||||
requestReasoningEffort: (effort: ReasoningEffort) => void;
|
||||
resetReasoningEffortToConfig: () => void;
|
||||
}
|
||||
|
||||
export interface StatusSummaryLinesInput {
|
||||
activeThreadId: ChatState["activeThread"]["id"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
}
|
||||
|
||||
export interface ModelStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedModel: ChatState["runtime"]["requestedModel"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
collaborationModeLabel: string;
|
||||
}
|
||||
|
||||
export interface EffortStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedReasoningEffort: ChatState["runtime"]["requestedReasoningEffort"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
}
|
||||
|
||||
export interface RestoredThreadTitleSnapshot {
|
||||
threadId: string;
|
||||
title: string | null;
|
||||
explicitName: string | null;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DisplayDetailSection, DisplayFileChange, DisplayFileMention, DisplayItem } from "../display/types";
|
||||
import type { DisplayDetailSection, DisplayFileChange, DisplayFileMention, DisplayItem, ExecutionState } from "../display/types";
|
||||
import type { CodexInput, CodexInputItem } from "../../../app-server/request-input";
|
||||
import type { AppServerFileUpdateChange, AppServerThreadItem, AppServerTurn } from "../../../app-server/turn-model";
|
||||
import { definedProp, truncate } from "../../../utils";
|
||||
|
|
@ -7,13 +7,6 @@ import { appServerUserItemText } from "../../../app-server/turn-model";
|
|||
import { agentDisplayItem } from "../display/agent";
|
||||
import { pathRelativeToRoot } from "../display/paths";
|
||||
import { normalizeProposedPlanMarkdown } from "../display/plan";
|
||||
import {
|
||||
commandExecutionState,
|
||||
dynamicToolCallExecutionState,
|
||||
imageGenerationExecutionState,
|
||||
mcpToolCallExecutionState,
|
||||
patchApplyExecutionState,
|
||||
} from "../display/state";
|
||||
import {
|
||||
bodyDetail,
|
||||
compactToolSummary,
|
||||
|
|
@ -42,6 +35,28 @@ type ReviewModeItem =
|
|||
| Extract<AppServerThreadItem, { type: "exitedReviewMode" }>;
|
||||
type ContextCompactionItem = Extract<AppServerThreadItem, { type: "contextCompaction" }>;
|
||||
type TextRange = [number, number];
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
const COMMAND_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
declined: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const PATCH_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
declined: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
const STANDARD_TOOL_STATES = {
|
||||
inProgress: "running",
|
||||
completed: "completed",
|
||||
failed: "failed",
|
||||
} as const satisfies ExecutionStateByStatus;
|
||||
|
||||
export function displayItemsFromTurns(turns: readonly AppServerTurn[]): DisplayItem[] {
|
||||
const sortedTurns = [...turns].sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
|
||||
|
|
@ -602,3 +617,38 @@ function pathRelativeToWorkspace(path: string, workspaceRoot?: string | null): s
|
|||
function assertNever(_item: never): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function commandExecutionState(status: string, exitCode?: number): ExecutionState {
|
||||
if (typeof exitCode === "number" && exitCode !== 0) return "failed";
|
||||
const state = executionStateFromStatus(status, COMMAND_STATES);
|
||||
if (state) return state;
|
||||
if (typeof exitCode === "number") return "completed";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function patchApplyExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, PATCH_STATES);
|
||||
}
|
||||
|
||||
export function mcpToolCallExecutionState(status: string): ExecutionState {
|
||||
return standardToolCallExecutionState(status);
|
||||
}
|
||||
|
||||
export function dynamicToolCallExecutionState(status: string, success?: boolean | null): ExecutionState {
|
||||
if (success === false) return "failed";
|
||||
const state = standardToolCallExecutionState(status);
|
||||
if (state) return state;
|
||||
return success === true ? "completed" : null;
|
||||
}
|
||||
|
||||
export function imageGenerationExecutionState(status: string): ExecutionState {
|
||||
return standardToolCallExecutionState(status);
|
||||
}
|
||||
|
||||
function standardToolCallExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, STANDARD_TOOL_STATES);
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { addOptional, nonEmptyString, permissionRows } from "../../display/permission-details";
|
||||
import { jsonPreview } from "../../../../utils";
|
||||
import { permissionRows } from "../../display/permission-rows";
|
||||
import type { RequestId } from "./model";
|
||||
|
||||
type SimpleApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel";
|
||||
|
|
@ -315,3 +316,23 @@ function grantedPermissions(requested: PermissionsApprovalParams["permissions"])
|
|||
if (requested.fileSystem) granted.fileSystem = requested.fileSystem;
|
||||
return granted;
|
||||
}
|
||||
|
||||
function addOptional(rows: { key: string; value: string }[], key: string, value: unknown): void {
|
||||
if (value === null || value === undefined) return;
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
rows.push({ key, value: stringValue(value) });
|
||||
}
|
||||
|
||||
function stringValue(value: unknown, fallback = ""): string {
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
||||
if (Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean")) {
|
||||
return value.join("\n");
|
||||
}
|
||||
if (value === null || value === undefined) return fallback;
|
||||
return jsonPreview(value);
|
||||
}
|
||||
|
||||
function nonEmptyString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
|
|
|||
30
src/features/chat/turn-diff/model.ts
Normal file
30
src/features/chat/turn-diff/model.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export interface ChatTurnDiffViewState {
|
||||
threadId: string;
|
||||
turnId: string;
|
||||
cwd: string | null;
|
||||
files: string[];
|
||||
diff: string;
|
||||
}
|
||||
|
||||
export type PersistedChatTurnDiffViewState = Omit<ChatTurnDiffViewState, "diff">;
|
||||
|
||||
export function persistedChatTurnDiffViewState(state: ChatTurnDiffViewState): PersistedChatTurnDiffViewState {
|
||||
return {
|
||||
threadId: state.threadId,
|
||||
turnId: state.turnId,
|
||||
cwd: state.cwd,
|
||||
files: [...state.files],
|
||||
};
|
||||
}
|
||||
|
||||
export function isPersistedChatTurnDiffViewState(value: unknown): value is PersistedChatTurnDiffViewState {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const record = value as Partial<PersistedChatTurnDiffViewState>;
|
||||
return (
|
||||
typeof record.threadId === "string" &&
|
||||
typeof record.turnId === "string" &&
|
||||
(typeof record.cwd === "string" || record.cwd === null) &&
|
||||
Array.isArray(record.files) &&
|
||||
record.files.every((file) => typeof file === "string")
|
||||
);
|
||||
}
|
||||
|
|
@ -6,42 +6,12 @@ import { displayDiffLines } from "../../../shared/diff/unified";
|
|||
import { IconButton } from "../../../shared/ui/components";
|
||||
import { renderUiRoot } from "../../../shared/ui/ui-root";
|
||||
import { shortThreadId } from "../../../utils";
|
||||
|
||||
export interface ChatTurnDiffViewState {
|
||||
threadId: string;
|
||||
turnId: string;
|
||||
cwd: string | null;
|
||||
files: string[];
|
||||
diff: string;
|
||||
}
|
||||
|
||||
export type PersistedChatTurnDiffViewState = Omit<ChatTurnDiffViewState, "diff">;
|
||||
import type { ChatTurnDiffViewState, PersistedChatTurnDiffViewState } from "./model";
|
||||
|
||||
export interface ChatTurnDiffViewActions {
|
||||
copyDiff?: () => void;
|
||||
}
|
||||
|
||||
export function persistedChatTurnDiffViewState(state: ChatTurnDiffViewState): PersistedChatTurnDiffViewState {
|
||||
return {
|
||||
threadId: state.threadId,
|
||||
turnId: state.turnId,
|
||||
cwd: state.cwd,
|
||||
files: [...state.files],
|
||||
};
|
||||
}
|
||||
|
||||
export function isPersistedChatTurnDiffViewState(value: unknown): value is PersistedChatTurnDiffViewState {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const record = value as Partial<PersistedChatTurnDiffViewState>;
|
||||
return (
|
||||
typeof record.threadId === "string" &&
|
||||
typeof record.turnId === "string" &&
|
||||
(typeof record.cwd === "string" || record.cwd === null) &&
|
||||
Array.isArray(record.files) &&
|
||||
record.files.every((file) => typeof file === "string")
|
||||
);
|
||||
}
|
||||
|
||||
export function renderChatTurnDiffView(
|
||||
parent: HTMLElement,
|
||||
state: ChatTurnDiffViewState | null,
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { ItemView, type ViewStateResult } from "obsidian";
|
||||
|
||||
import { VIEW_TYPE_CODEX_TURN_DIFF } from "../../constants";
|
||||
import { copyTextWithNotice } from "../../shared/ui/clipboard";
|
||||
import { unmountUiRoot } from "../../shared/ui/ui-root";
|
||||
import { VIEW_TYPE_CODEX_TURN_DIFF } from "../../../constants";
|
||||
import { copyTextWithNotice } from "../../../shared/ui/clipboard";
|
||||
import { unmountUiRoot } from "../../../shared/ui/ui-root";
|
||||
import {
|
||||
isPersistedChatTurnDiffViewState,
|
||||
persistedChatTurnDiffViewState,
|
||||
renderChatTurnDiffView,
|
||||
type PersistedChatTurnDiffViewState,
|
||||
type ChatTurnDiffViewState,
|
||||
} from "./ui/turn-diff";
|
||||
} from "./model";
|
||||
import { renderChatTurnDiffView } from "./render";
|
||||
|
||||
export class CodexChatTurnDiffView extends ItemView {
|
||||
private metadata: PersistedChatTurnDiffViewState | null = null;
|
||||
|
|
@ -3,7 +3,7 @@ import type { ButtonHTMLAttributes, ComponentChild as UiNode, Ref } from "preact
|
|||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import type { ComposerSuggestion } from "../conversation/composer/suggestions";
|
||||
import type { ComposerMetaViewModel, RuntimeChoice } from "../panel/view-model/types";
|
||||
import type { ComposerMetaViewModel, RuntimeChoice } from "../panel/view-model/composer";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
import { syncTextareaHeight } from "../../../shared/ui/textarea-autogrow";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { ChatTurnLifecycleState } from "../../state/reducer";
|
|||
import type { PendingRequestSnapshot } from "../../state/selectors";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import type { PendingRequestMessageActions } from "../pending-request-message";
|
||||
import type { ChatTurnDiffViewState } from "../turn-diff";
|
||||
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
||||
|
||||
export interface MessageStreamBlock {
|
||||
key: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { type ComponentChild as UiNode, type Ref } from "preact";
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { executionState } from "../../display/state";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import type { DisplayItem, ExecutionState } from "../../display/types";
|
||||
import { MESSAGE_CONTENT_RENDERED_EVENT } from "../message-content-events";
|
||||
import type { MessageContentContext, MessageItemContext, RenderableTextItem } from "./context";
|
||||
import { MessageRole } from "./message-actions";
|
||||
|
|
@ -18,7 +17,7 @@ function MessageItem({ item, context }: { item: RenderableTextItem; context: Mes
|
|||
const collapsible = isCollapsibleUserMessage(item);
|
||||
const details = "details" in item ? item.details : undefined;
|
||||
return (
|
||||
<div className={`${messageClass(item)}${executionClassName(executionState(item))}`}>
|
||||
<div className={`${messageClass(item)}${executionClassName(item.executionState ?? null)}`}>
|
||||
<MessageRole item={item} context={context} />
|
||||
{collapsible ? (
|
||||
<CollapsibleMessageContent item={item} context={context} />
|
||||
|
|
@ -168,7 +167,7 @@ function contentRenderMode(item: RenderableTextItem): "markdown" | "text" {
|
|||
return item.messageKind === "proposedPlan" && item.messageState === "streaming" ? "text" : "markdown";
|
||||
}
|
||||
|
||||
function executionClassName(state: ReturnType<typeof executionState>): string {
|
||||
function executionClassName(state: ExecutionState): string {
|
||||
return state ? ` codex-panel__execution codex-panel__execution--${state}` : "";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ChatAction, ChatState } from "../../state/reducer";
|
||||
import type { ChatMessageStreamActionPort, ChatMessageStreamContextPort, ChatMessageStreamRequestPort } from "./context-builder";
|
||||
import type { ChatMessageStreamActionPort, ChatMessageStreamContextPort, ChatMessageStreamRequestPort } from "./view-model";
|
||||
|
||||
export interface MessageStreamContextPortOptions {
|
||||
vaultPath: string;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ChatState } from "../../state/reducer";
|
||||
import type { MessageStreamScrollIntent, MessageStreamVirtualizerHandle } from "../message-virtualizer";
|
||||
import { messageStreamBlocks } from "./blocks";
|
||||
import { createMessageStreamContext, type ChatMessageStreamContextPort } from "./context-builder";
|
||||
import { messageStreamBlocks } from "./stream-blocks";
|
||||
import { createMessageStreamContext, type ChatMessageStreamContextPort } from "./view-model";
|
||||
import type { MessageStreamRenderState } from "./render";
|
||||
|
||||
export interface MessageStreamRenderStateOptions {
|
||||
|
|
@ -5,11 +5,11 @@ import { copyTextWithNotice } from "../../../../shared/ui/clipboard";
|
|||
import type { ChatAction, ChatState, ChatStateStore } from "../../state/reducer";
|
||||
import type { ComposerBoundaryScrollAction } from "../../conversation/composer/boundary-scroll";
|
||||
import type { MessageStreamScrollIntent, MessageStreamVirtualizerHandle } from "../message-virtualizer";
|
||||
import type { ChatMessageStreamActionPort, ChatMessageStreamContextPort, ChatMessageStreamRequestPort } from "./context-builder";
|
||||
import { createMessageStreamContextPort } from "./context-port";
|
||||
import type { ChatMessageStreamActionPort, ChatMessageStreamContextPort, ChatMessageStreamRequestPort } from "./view-model";
|
||||
import { createMessageStreamContextPort } from "./ports";
|
||||
import { MarkdownMessageRenderer } from "./markdown-renderer";
|
||||
import { messageStreamBlocksNode, type MessageStreamRenderState } from "./render";
|
||||
import { createMessageStreamRenderState } from "./render-state";
|
||||
import { createMessageStreamRenderState } from "./render-model";
|
||||
|
||||
interface ChatMessageRendererObsidianPort {
|
||||
app: App;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
isRollbackCandidateItem,
|
||||
rollbackCandidateFromItems,
|
||||
} from "../../display/action-candidates";
|
||||
import type { ChatTurnDiffViewState } from "../turn-diff";
|
||||
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
||||
import type { PendingRequestMessageActions } from "../pending-request-message";
|
||||
import type { MessageStreamContext } from "./context";
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ import type { ComponentChild as UiNode } from "preact";
|
|||
import { useComputed } from "@preact/signals";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { chatPanelGoalProps, chatPanelToolbarViewModel } from "./region-view-models";
|
||||
import { chatPanelGoalProps, chatPanelToolbarViewModel } from "../panel/region-view-models";
|
||||
import { goalBannerNode } from "./goal-banner";
|
||||
import { toolbarNode } from "./toolbar";
|
||||
import type { ChatPanelGoalPorts, ChatPanelToolbarPorts } from "../panel/ui-ports";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { ButtonHTMLAttributes, ComponentChild as UiNode, TargetedKeyboardEvent } from "preact";
|
||||
import { useLayoutEffect, useRef } from "preact/hooks";
|
||||
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../runtime/status-summary";
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../display/runtime-status";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
import type { ToolbarDiagnosticSection, ToolbarThreadRow, ToolbarViewModel } from "../panel/view-model/types";
|
||||
import type { ToolbarDiagnosticSection, ToolbarThreadRow, ToolbarViewModel } from "../panel/view-model/toolbar";
|
||||
|
||||
type ButtonProps = ButtonHTMLAttributes & {
|
||||
disabled?: boolean | undefined;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
import { useLayoutEffect, useState } from "preact/hooks";
|
||||
|
||||
import { activeAgentRunSummary } from "../display/agent";
|
||||
import { executionState } from "../display/state";
|
||||
import { activeAgentRunSummary, agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel } from "../display/agent";
|
||||
import { taskStatusMarker } from "../display/plan";
|
||||
import type {
|
||||
AgentDisplayItem,
|
||||
AgentRunSummary,
|
||||
AgentRunSummaryAgent,
|
||||
ContextCompactionDisplayItem,
|
||||
DisplayItem,
|
||||
ExecutionState,
|
||||
ReasoningDisplayItem,
|
||||
TaskProgressDisplayItem,
|
||||
} from "../display/types";
|
||||
import { agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel, taskStatusMarker } from "../display/labels";
|
||||
import { activeTurnId, type ChatTurnLifecycleState } from "../state/reducer";
|
||||
import { createWorkMessageClassName } from "./work-message";
|
||||
import { shortThreadId, truncate } from "../../../utils";
|
||||
|
|
@ -59,7 +59,7 @@ function AgentRunSummaryItem({ summary }: { summary: AgentRunSummary }): UiNode
|
|||
|
||||
function TaskProgressItem({ item }: { item: TaskProgressDisplayItem }): UiNode {
|
||||
return (
|
||||
<WorkMessage label="tasks" className="codex-panel__task-progress" state={executionState(item)}>
|
||||
<WorkMessage label="tasks" className="codex-panel__task-progress" state={item.executionState ?? null}>
|
||||
{item.explanation ? <div className="codex-panel__tool-summary">{item.explanation}</div> : null}
|
||||
{item.steps.length === 0 ? (
|
||||
<div className="codex-panel__tool-summary">Plan updated</div>
|
||||
|
|
@ -93,7 +93,11 @@ function AgentItem({ item, context }: { item: AgentDisplayItem; context: WorkIte
|
|||
setDetailsOpen(context.openDetails.has(detailsKey));
|
||||
}, [context.openDetails, detailsKey]);
|
||||
return (
|
||||
<WorkMessage label="agent" className={`codex-panel__agent-activity${detailsOpen ? " is-open" : ""}`} state={executionState(item)}>
|
||||
<WorkMessage
|
||||
label="agent"
|
||||
className={`codex-panel__agent-activity${detailsOpen ? " is-open" : ""}`}
|
||||
state={item.executionState ?? null}
|
||||
>
|
||||
<div className="codex-panel__tool-summary codex-panel__agent-activity-summary">{agentSummaryText(item)}</div>
|
||||
<details
|
||||
className="codex-panel__output codex-panel__agent-details"
|
||||
|
|
@ -169,7 +173,7 @@ function WorkMessage({
|
|||
}: {
|
||||
label: string;
|
||||
className: string;
|
||||
state: ReturnType<typeof executionState>;
|
||||
state: ExecutionState;
|
||||
children: UiNode;
|
||||
}): UiNode {
|
||||
const classes = [createWorkMessageClassName(className), state ? `codex-panel__execution codex-panel__execution--${state}` : ""]
|
||||
|
|
@ -240,7 +244,7 @@ function isLongAgentMessage(message: string): boolean {
|
|||
function isReasoningActive(item: ReasoningDisplayItem, context: WorkItemContext): boolean {
|
||||
const activeTurn = workItemsActiveTurnId(context);
|
||||
if (!activeTurn || item.turnId !== activeTurn) return false;
|
||||
if (executionState(item) === "completed") return false;
|
||||
if (item.executionState === "completed") return false;
|
||||
const latestActiveTurnItem = [...context.displayItems].reverse().find((candidate) => candidate.turnId === activeTurn);
|
||||
return latestActiveTurnItem?.id === item.id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
} from "./panel/view-model/runtime";
|
||||
} from "./display/runtime-status";
|
||||
import { runtimeSnapshotForChatState } from "./runtime/snapshot";
|
||||
import { activeThreadTitle as buildActiveThreadTitle, chatViewDisplayTitle } from "./panel/view-model/thread-title";
|
||||
import { connectionDiagnosticsModel } from "./panel/view-model/toolbar";
|
||||
|
|
@ -26,7 +26,11 @@ import { ChatMessageScrollIntentController } from "./panel/message-scroll-intent
|
|||
import type { ChatControllerCompositionPorts } from "./panel/controller-ports";
|
||||
import { createChatViewControllers, type ChatViewControllers } from "./panel/composition";
|
||||
import type { ChatPanelComposerPorts, ChatPanelGoalPorts, ChatPanelStatePort, ChatPanelToolbarPorts } from "./panel/ui-ports";
|
||||
import { chatPanelComposerMetaViewModel, chatPanelComposerPlaceholder, chatPanelPendingRequestsSignature } from "./ui/region-view-models";
|
||||
import {
|
||||
chatPanelComposerMetaViewModel,
|
||||
chatPanelComposerPlaceholder,
|
||||
chatPanelPendingRequestsSignature,
|
||||
} from "./panel/region-view-models";
|
||||
import {
|
||||
chatPanelComposerRegionNode,
|
||||
chatPanelGoalRegionNode,
|
||||
|
|
@ -639,6 +643,7 @@ export class CodexChatView extends ItemView {
|
|||
return buildStatusSummaryLines({
|
||||
activeThreadId: this.state.activeThread.id,
|
||||
snapshot: this.runtimeSnapshot(),
|
||||
nowMs: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { VIEW_TYPE_CODEX_PANEL, VIEW_TYPE_CODEX_THREADS, VIEW_TYPE_CODEX_TURN_DI
|
|||
import { registerSelectionRewriteCommand } from "./features/selection-rewrite/command";
|
||||
import { CodexChatView } from "./features/chat/view";
|
||||
import type { CodexChatHost } from "./features/chat/chat-host";
|
||||
import { CodexChatTurnDiffView } from "./features/chat/chat-turn-diff-view";
|
||||
import { CodexChatTurnDiffView } from "./features/chat/turn-diff/view";
|
||||
import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal";
|
||||
import { CodexThreadsView, type CodexThreadsHost } from "./features/threads-view/view";
|
||||
import { SharedAppServerCache } from "./app-server/shared-cache";
|
||||
import type { SharedAppServerCacheContext } from "./app-server/shared-cache-state";
|
||||
import { DEFAULT_SETTINGS, getVaultPath, normalizeSettings, settingsMatchNormalizedData, type CodexPanelSettings } from "./settings/model";
|
||||
import { CodexPanelSettingTab, type CodexPanelSettingTabHost } from "./settings/tab";
|
||||
import { persistedChatTurnDiffViewState, type ChatTurnDiffViewState } from "./features/chat/ui/turn-diff";
|
||||
import { persistedChatTurnDiffViewState, type ChatTurnDiffViewState } from "./features/chat/turn-diff/model";
|
||||
import { WorkspacePanelCoordinator } from "./workspace/panel-coordinator";
|
||||
import { ThreadSurfaceCoordinator } from "./workspace/thread-surface-coordinator";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { activeAgentRunSummary } from "../../../../src/features/chat/display/agent";
|
||||
import { activeAgentRunSummary, collabAgentStateExecutionState } from "../../../../src/features/chat/display/agent";
|
||||
import { displayBlocksForItems } from "../../../../src/features/chat/display/blocks";
|
||||
import {
|
||||
appendAssistantDelta,
|
||||
|
|
@ -10,20 +10,20 @@ import {
|
|||
appendToolOutput,
|
||||
upsertDisplayItem,
|
||||
} from "../../../../src/features/chat/display/stream-updates";
|
||||
import { normalizeProposedPlanMarkdown, planProgressDisplayItem } from "../../../../src/features/chat/display/plan";
|
||||
import { pathRelativeToRoot } from "../../../../src/features/chat/display/paths";
|
||||
import { permissionRows } from "../../../../src/features/chat/display/permission-details";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../../../../src/features/chat/display/review";
|
||||
import {
|
||||
autoReviewExecutionState,
|
||||
collabAgentStateExecutionState,
|
||||
normalizeProposedPlanMarkdown,
|
||||
planProgressDisplayItem,
|
||||
taskProgressExecutionState,
|
||||
} from "../../../../src/features/chat/display/plan";
|
||||
import { pathRelativeToRoot } from "../../../../src/features/chat/display/paths";
|
||||
import { permissionRows } from "../../../../src/features/chat/display/permission-rows";
|
||||
import { autoReviewExecutionState, createAutoReviewResultItem, createReviewResultItem } from "../../../../src/features/chat/display/review";
|
||||
import {
|
||||
commandExecutionState,
|
||||
dynamicToolCallExecutionState,
|
||||
executionState,
|
||||
mcpToolCallExecutionState,
|
||||
patchApplyExecutionState,
|
||||
taskProgressExecutionState,
|
||||
} from "../../../../src/features/chat/display/state";
|
||||
} from "../../../../src/features/chat/protocol/display-items";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns } from "../../../../src/features/chat/protocol/display-items";
|
||||
import { referencedThreadPrompt } from "../../../../src/domain/threads/reference";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
|
|
@ -1555,17 +1555,16 @@ describe("execution state uses typed status adapters before rendered text", () =
|
|||
|
||||
it("does not infer unknown status strings with broad matching", () => {
|
||||
expect(patchApplyExecutionState("done_with_errors")).toBeNull();
|
||||
expect(
|
||||
executionState({
|
||||
id: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "Command",
|
||||
command: "npm test",
|
||||
cwd: "/vault",
|
||||
status: "done_with_errors",
|
||||
}),
|
||||
).toBeNull();
|
||||
const item: DisplayItem = {
|
||||
id: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "Command",
|
||||
command: "npm test",
|
||||
cwd: "/vault",
|
||||
status: "done_with_errors",
|
||||
};
|
||||
expect(item.executionState ?? null).toBeNull();
|
||||
});
|
||||
|
||||
it("does not overwrite streamed output with an empty completed item", () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, type ChatAction } from "../../../../../src/features/chat/state/reducer";
|
||||
import { createMessageStreamContextPort } from "../../../../../src/features/chat/ui/message-stream/context-port";
|
||||
import { createMessageStreamContextPort } from "../../../../../src/features/chat/ui/message-stream/ports";
|
||||
|
||||
describe("message stream context port", () => {
|
||||
it("closes other fork action details before opening a fork action detail", () => {
|
||||
|
|
@ -6,7 +6,7 @@ import type { PendingApproval } from "../../../../../src/features/chat/protocol/
|
|||
import type { PendingUserInput } from "../../../../../src/features/chat/protocol/requests/user-input";
|
||||
import { pendingRequestMessageNode, type PendingRequestMessageActions } from "../../../../../src/features/chat/ui/pending-request-message";
|
||||
import type { ChatTurnLifecycleState } from "../../../../../src/features/chat/state/reducer";
|
||||
import { messageStreamBlocks as rawMessageStreamBlocks } from "../../../../../src/features/chat/ui/message-stream/blocks";
|
||||
import { messageStreamBlocks as rawMessageStreamBlocks } from "../../../../../src/features/chat/ui/message-stream/stream-blocks";
|
||||
import type { MessageStreamBlock } from "../../../../../src/features/chat/ui/message-stream/context";
|
||||
import { messageStreamBlocksNode } from "../../../../../src/features/chat/ui/message-stream/render";
|
||||
import { renderUiRoot, unmountUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
type ComposerCallbacks,
|
||||
} from "../../../../../src/features/chat/ui/composer";
|
||||
import type { ComposerSuggestion } from "../../../../../src/features/chat/conversation/composer/suggestions";
|
||||
import type { ComposerMetaViewModel } from "../../../../../src/features/chat/panel/view-model/types";
|
||||
import type { ComposerMetaViewModel } from "../../../../../src/features/chat/panel/view-model/composer";
|
||||
import { renderUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
import { waitForAsyncWork } from "../../../../support/async";
|
||||
import { changeInputValue, composerSuggestionScrollFixture, installObsidianDomShims } from "../../../../support/dom";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { ToolbarViewModel } from "../../../../../src/features/chat/panel/view-model/types";
|
||||
import type { ToolbarViewModel } from "../../../../../src/features/chat/panel/view-model/toolbar";
|
||||
import { toolbarNode } from "../../../../../src/features/chat/ui/toolbar";
|
||||
import { renderUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
import { changeInputValue, installObsidianDomShims } from "../../../../support/dom";
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { WorkspaceLeaf } from "obsidian";
|
||||
|
||||
import { CodexChatTurnDiffView } from "../../../../../src/features/chat/chat-turn-diff-view";
|
||||
import { persistedChatTurnDiffViewState, renderChatTurnDiffView } from "../../../../../src/features/chat/ui/turn-diff";
|
||||
import { persistedChatTurnDiffViewState } from "../../../../../src/features/chat/turn-diff/model";
|
||||
import { renderChatTurnDiffView } from "../../../../../src/features/chat/turn-diff/render";
|
||||
import { CodexChatTurnDiffView } from "../../../../../src/features/chat/turn-diff/view";
|
||||
import { installObsidianDomShims } from "../../../../support/dom";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
|
|
|||
|
|
@ -8,12 +8,8 @@ import {
|
|||
} from "../../../src/app-server/runtime-config";
|
||||
import { createChatState } from "../../../src/features/chat/state/reducer";
|
||||
import { composerMetaViewModel, composerPlaceholder } from "../../../src/features/chat/panel/view-model/composer";
|
||||
import {
|
||||
effortStatusLines,
|
||||
runtimeComposerChoices,
|
||||
modelStatusLines,
|
||||
statusSummaryLines,
|
||||
} from "../../../src/features/chat/panel/view-model/runtime";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../src/features/chat/display/runtime-status";
|
||||
import { runtimeComposerChoices } from "../../../src/features/chat/panel/view-model/composer";
|
||||
import { runtimeSnapshotForChatState } from "../../../src/features/chat/runtime/snapshot";
|
||||
import {
|
||||
activeComposerThreadName,
|
||||
|
|
@ -24,7 +20,7 @@ import { toolbarViewModel } from "../../../src/features/chat/panel/view-model/to
|
|||
import type { ChatState } from "../../../src/features/chat/state/reducer";
|
||||
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
|
||||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import { chatPanelComposerMetaViewModel, chatPanelGoalProps } from "../../../src/features/chat/ui/region-view-models";
|
||||
import { chatPanelComposerMetaViewModel, chatPanelGoalProps } from "../../../src/features/chat/panel/region-view-models";
|
||||
import type { ChatPanelComposerPorts, ChatPanelGoalPorts } from "../../../src/features/chat/panel/ui-ports";
|
||||
import type { ThreadGoal } from "../../../src/app-server/thread-goal";
|
||||
|
||||
|
|
@ -186,7 +182,7 @@ describe("chat view model", () => {
|
|||
state.connection.availableModels = [modelFixture("gpt-5.5")];
|
||||
const snapshot = runtimeSnapshotFixture(state);
|
||||
|
||||
expect(statusSummaryLines({ activeThreadId: state.activeThread.id, snapshot })[1]).toBe("Thread: thread-1");
|
||||
expect(statusSummaryLines({ activeThreadId: state.activeThread.id, snapshot, nowMs: 0 })[1]).toBe("Thread: thread-1");
|
||||
expect(
|
||||
modelStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
requestedTurnCollaborationModeSettings,
|
||||
serviceTierRequestForThreadStart,
|
||||
} from "../../src/features/chat/runtime/turn-settings";
|
||||
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/runtime/status-summary";
|
||||
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/display/runtime-status";
|
||||
|
||||
describe("runtime settings", () => {
|
||||
it("parses model overrides", () => {
|
||||
|
|
@ -660,6 +660,7 @@ describe("runtime settings", () => {
|
|||
rateLimitReachedType: "rate_limit_reached",
|
||||
},
|
||||
}),
|
||||
0,
|
||||
),
|
||||
).toMatchObject({
|
||||
rows: [{ percent: 95, resetLabel: null }],
|
||||
|
|
@ -678,6 +679,7 @@ describe("runtime settings", () => {
|
|||
rateLimitReachedType: null,
|
||||
},
|
||||
}),
|
||||
0,
|
||||
),
|
||||
).toMatchObject({
|
||||
rows: [
|
||||
|
|
|
|||
Loading…
Reference in a new issue