mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Reorganize chat display and thread modules
This commit is contained in:
parent
230f35627f
commit
c516ccee6b
95 changed files with 718 additions and 714 deletions
|
|
@ -103,8 +103,8 @@ const chatExternalDomBridgeFiles = [
|
|||
"src/features/chat/ui/message-virtualizer.ts",
|
||||
];
|
||||
const chatPreactDomBridgeFiles = [
|
||||
"src/features/chat/ui/message-stream/message-actions.tsx",
|
||||
"src/features/chat/ui/message-stream/message-item.tsx",
|
||||
"src/features/chat/ui/message-stream/text-item-actions.tsx",
|
||||
"src/features/chat/ui/message-stream/text-item.tsx",
|
||||
"src/features/chat/ui/message-stream/render.tsx",
|
||||
"src/features/chat/ui/composer.tsx",
|
||||
"src/features/chat/ui/goal-banner.tsx",
|
||||
|
|
|
|||
|
|
@ -81,12 +81,12 @@ export function createChatControllerCompositionActions(
|
|||
const status = {
|
||||
set: ports.status.set,
|
||||
addSystemMessage: (text: string) => {
|
||||
ports.state.stateStore.dispatch({ type: "transcript/system-message-added", item: ports.state.systemItem(text) });
|
||||
ports.state.stateStore.dispatch({ type: "transcript/system-item-added", item: ports.state.systemItem(text) });
|
||||
render.now();
|
||||
},
|
||||
addStructuredSystemMessage: (text: string, details: DisplayDetailSection[]) => {
|
||||
ports.state.stateStore.dispatch({
|
||||
type: "transcript/system-message-added",
|
||||
type: "transcript/system-item-added",
|
||||
item: ports.state.structuredSystemItem(text, details),
|
||||
});
|
||||
render.now();
|
||||
|
|
|
|||
|
|
@ -4,21 +4,21 @@ import type { ChatServerMetadataActions } from "./connection/server-actions/meta
|
|||
import type { ChatServerThreadActions } from "./connection/server-actions/threads";
|
||||
import type { ChatComposerController } from "./conversation/composer/controller";
|
||||
import type { ChatInboundController } from "./protocol/inbound/controller";
|
||||
import type { ChatThreadGoalActions } from "./threads/thread-goal-actions";
|
||||
import type { GoalActions } from "./threads/goal-actions";
|
||||
import { createChatRuntimeSettingsActions, type ChatRuntimeSettingsActions } from "./runtime/settings-actions";
|
||||
import type { ChatThreadActions } from "./threads/thread-actions";
|
||||
import type { ThreadHistoryController } from "./threads/thread-history-controller";
|
||||
import type { ThreadRenameController } from "./threads/thread-rename-controller";
|
||||
import type { ChatThreadActions } from "./threads/actions";
|
||||
import type { HistoryController } from "./threads/history-controller";
|
||||
import type { RenameController } from "./threads/rename-controller";
|
||||
import type { ToolbarPanelController } from "./panel/regions/toolbar";
|
||||
import type { ChatConnectionController } from "./connection/connection-controller";
|
||||
import type { ChatReconnectActions } from "./connection/reconnect-actions";
|
||||
import type { PendingRequestController } from "./conversation/pending-requests/controller";
|
||||
import { rejectServerRequest, respondToServerRequest } from "./protocol/requests/server-request-responder";
|
||||
import type { ComposerSubmissionActions } from "./conversation/turns/composer-submission-actions";
|
||||
import type { RestoredThreadController } from "./threads/restored-thread-controller";
|
||||
import type { ThreadIdentitySync } from "./threads/thread-identity-sync";
|
||||
import type { ThreadResumeController } from "./threads/thread-resume-controller";
|
||||
import type { ThreadSelectionActions } from "./threads/thread-selection-actions";
|
||||
import type { RestorationController } from "./threads/restoration-controller";
|
||||
import type { IdentitySync } from "./threads/identity-sync";
|
||||
import type { ResumeController } from "./threads/resume-controller";
|
||||
import type { SelectionActions } from "./threads/selection-actions";
|
||||
import type { ChatViewRenderController } from "./panel/view-render-controller";
|
||||
import type { ChatMessageRenderer } from "./ui/message-stream/renderer";
|
||||
import type { ChatControllerCompositionPorts } from "./composition-ports";
|
||||
|
|
@ -53,17 +53,17 @@ export interface ChatViewControllers {
|
|||
diagnostics: ChatServerDiagnosticsActions;
|
||||
};
|
||||
thread: {
|
||||
history: ThreadHistoryController;
|
||||
resume: ThreadResumeController;
|
||||
history: HistoryController;
|
||||
resume: ResumeController;
|
||||
actions: ChatThreadActions;
|
||||
restored: RestoredThreadController;
|
||||
identity: ThreadIdentitySync;
|
||||
rename: ThreadRenameController;
|
||||
selection: ThreadSelectionActions;
|
||||
restoration: RestorationController;
|
||||
identity: IdentitySync;
|
||||
rename: RenameController;
|
||||
selection: SelectionActions;
|
||||
};
|
||||
runtime: {
|
||||
settings: ChatRuntimeSettingsActions;
|
||||
goals: ChatThreadGoalActions;
|
||||
goals: GoalActions;
|
||||
};
|
||||
requests: {
|
||||
pending: PendingRequestController;
|
||||
|
|
@ -105,7 +105,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
},
|
||||
});
|
||||
let connectionController: ChatConnectionController | null = null;
|
||||
let threadSelection: ThreadSelectionActions | null = null;
|
||||
let selection: SelectionActions | null = null;
|
||||
let composerController: ChatComposerController | null = null;
|
||||
const actions = createChatControllerCompositionActions(
|
||||
{
|
||||
|
|
@ -121,7 +121,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
ensureConnected: () => requireComposedController(connectionController, "connection controller").ensureConnected(),
|
||||
refreshThreads: () => requireComposedController(connectionController, "connection controller").refreshThreads(),
|
||||
refreshSkills: (forceReload) => requireComposedController(connectionController, "connection controller").refreshSkills(forceReload),
|
||||
selectThread: (threadId) => requireComposedController(threadSelection, "thread selection actions").selectThread(threadId),
|
||||
selectThread: (threadId) => requireComposedController(selection, "selection actions").selectThread(threadId),
|
||||
setComposerText: (text) => {
|
||||
requireComposedController(composerController, "composer controller").setDraft(text, { focus: true });
|
||||
},
|
||||
|
|
@ -182,8 +182,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
connection,
|
||||
},
|
||||
);
|
||||
const { history, threadActions, goals, threadIdentity } = threadControllers;
|
||||
const { restoredThread, threadResume, threadRename } = threadControllers;
|
||||
const { history, actions: threadActions, goals, identity, restoration, resume, rename } = threadControllers;
|
||||
const lifecycleActions = {
|
||||
deferredTasks: ports.lifecycle.deferredTasks,
|
||||
resumeWork: ports.lifecycle.resumeWork,
|
||||
|
|
@ -210,10 +209,10 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
render: actions.render,
|
||||
thread: {
|
||||
restorePlaceholder: (restoredThreadState) => {
|
||||
restoredThread.restore(restoredThreadState);
|
||||
restoration.restore(restoredThreadState);
|
||||
},
|
||||
clearRestoredLifecycle: () => {
|
||||
restoredThread.clear();
|
||||
restoration.clear();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -221,7 +220,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
threadActions,
|
||||
},
|
||||
);
|
||||
threadSelection = createThreadSelectionActionGroup(
|
||||
selection = createThreadSelectionActionGroup(
|
||||
{
|
||||
plugin: {
|
||||
focusThreadInOpenView: (threadId) => ports.plugin.focusThreadInOpenView(threadId),
|
||||
|
|
@ -230,7 +229,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
stateStore: ports.state.stateStore,
|
||||
},
|
||||
thread: {
|
||||
resumeThread: (threadId) => threadResume.resumeThread(threadId),
|
||||
resumeThread: (threadId) => resume.resumeThread(threadId),
|
||||
},
|
||||
status: actions.status,
|
||||
},
|
||||
|
|
@ -239,7 +238,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
toolbarPanels.closeForThreadSelection();
|
||||
},
|
||||
},
|
||||
).threadSelection;
|
||||
).selection;
|
||||
const { reconnectActions } = createChatReconnectControllerGroup(
|
||||
{
|
||||
state: {
|
||||
|
|
@ -250,7 +249,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
render: actions.render,
|
||||
status: actions.status,
|
||||
thread: {
|
||||
resumeThread: (threadId) => threadResume.resumeThread(threadId),
|
||||
resumeThread: (threadId) => resume.resumeThread(threadId),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -309,7 +308,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
{
|
||||
serverMetadata,
|
||||
serverDiagnostics,
|
||||
threadRename,
|
||||
rename,
|
||||
respondToServerRequest: (requestId, result) => respondToServerRequest(serverRequestHost, requestId, result),
|
||||
rejectServerRequest: (requestId, code, message) => rejectServerRequest(serverRequestHost, requestId, code, message),
|
||||
},
|
||||
|
|
@ -331,7 +330,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
loadSharedThreadList: ports.thread.loadSharedThreadList,
|
||||
refreshTabHeader: ports.thread.refreshTabHeader,
|
||||
resetTurnPresence: (hadTurns) => {
|
||||
threadRename.resetThreadTurnPresence(hadTurns);
|
||||
rename.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
},
|
||||
status: actions.status,
|
||||
|
|
@ -399,7 +398,7 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
selectThread: actions.thread.selectThread,
|
||||
notifyIdentityChanged: ports.thread.notifyIdentityChanged,
|
||||
resetTurnPresence: (hadTurns) => {
|
||||
threadRename.resetThreadTurnPresence(hadTurns);
|
||||
rename.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
},
|
||||
status: actions.status,
|
||||
|
|
@ -423,7 +422,6 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
serverThreads,
|
||||
runtimeSettings,
|
||||
threadActions,
|
||||
threadRename,
|
||||
reconnectActions,
|
||||
goals,
|
||||
history,
|
||||
|
|
@ -493,12 +491,12 @@ export function createChatViewControllers(ports: ChatControllerCompositionPorts)
|
|||
},
|
||||
thread: {
|
||||
history,
|
||||
resume: threadResume,
|
||||
resume,
|
||||
actions: threadActions,
|
||||
restored: restoredThread,
|
||||
identity: threadIdentity,
|
||||
rename: threadRename,
|
||||
selection: threadSelection,
|
||||
restoration,
|
||||
identity,
|
||||
rename,
|
||||
selection,
|
||||
},
|
||||
runtime: {
|
||||
settings: runtimeSettings,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import { createChatServerThreadActions } from "./server-actions/threads";
|
|||
import { ChatConnectionController } from "./connection-controller";
|
||||
import { createChatReconnectActions } from "./reconnect-actions";
|
||||
import type { rejectServerRequest, respondToServerRequest } from "../protocol/requests/server-request-responder";
|
||||
import type { ChatThreadGoalActions } from "../threads/thread-goal-actions";
|
||||
import type { ThreadRenameController } from "../threads/thread-rename-controller";
|
||||
import type { GoalActions } from "../threads/goal-actions";
|
||||
import type { RenameController } from "../threads/rename-controller";
|
||||
import { ChatInboundController } from "../protocol/inbound/controller";
|
||||
import type { ChatConnectionWorkTracker } from "../lifecycle";
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ export function createChatServerActionControllers(
|
|||
context: ChatServerActionControllerPorts,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
goals: ChatThreadGoalActions;
|
||||
goals: GoalActions;
|
||||
},
|
||||
) {
|
||||
const { plugin, runtime } = context;
|
||||
|
|
@ -97,7 +97,7 @@ export function createChatInboundController(
|
|||
refs: {
|
||||
serverMetadata: ChatServerMetadataActions;
|
||||
serverDiagnostics: ChatServerDiagnosticsActions;
|
||||
threadRename: ThreadRenameController;
|
||||
rename: RenameController;
|
||||
respondToServerRequest: (requestId: Parameters<typeof respondToServerRequest>[1], result: unknown) => boolean;
|
||||
rejectServerRequest: (requestId: Parameters<typeof rejectServerRequest>[1], code: number, message: string) => boolean;
|
||||
},
|
||||
|
|
@ -114,7 +114,7 @@ export function createChatInboundController(
|
|||
refreshSkills: (forceReload) => void thread.refreshSkills(forceReload),
|
||||
publishAppServerMetadata: thread.publishAppServerMetadataSnapshot,
|
||||
maybeNameThread: (threadId, turnId, completedSummary) => {
|
||||
refs.threadRename.maybeAutoNameThread(threadId, turnId, completedSummary);
|
||||
refs.rename.maybeAutoNameThread(threadId, turnId, completedSummary);
|
||||
},
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
type McpServerStatusSummary,
|
||||
} from "../../../../app-server/diagnostics";
|
||||
import type { SharedAppServerMetadata } from "../../../../app-server/shared-cache-state";
|
||||
import { mcpStatusLines as buildMcpStatusLines } from "../../display/diagnostics";
|
||||
import { mcpStatusLines as buildMcpStatusLines } from "../../display/status/diagnostics";
|
||||
import { cloneAppServerDiagnostics, type ChatServerActionHost } from "./host";
|
||||
|
||||
interface RefreshDiagnosticProbesOptions {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { Thread } from "../../../../domain/threads/model";
|
|||
import type { RuntimeSnapshot } from "../../runtime/snapshot";
|
||||
import { runtimeConfigOrDefault } from "../../runtime/effective";
|
||||
import { serviceTierRequestForThreadStart } from "../../runtime/thread-settings";
|
||||
import { resumedThreadActionFromAppServerResponse } from "../../threads/thread-resume";
|
||||
import { resumedThreadActionFromAppServerResponse } from "../../threads/resume";
|
||||
import type { ChatServerActionHost } from "./host";
|
||||
import type { ChatState } from "../../state/reducer";
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import { createComposerSubmissionActions } from "./turns/composer-submission-act
|
|||
import { createPlanImplementationActions } from "./turns/plan-implementation-actions";
|
||||
import { createSlashCommandActions } from "./turns/slash-command-actions";
|
||||
import { TurnSubmissionController } from "./turns/turn-submission-controller";
|
||||
import type { ChatThreadActions } from "../threads/thread-actions";
|
||||
import type { ChatThreadGoalActions } from "../threads/thread-goal-actions";
|
||||
import type { ThreadHistoryController } from "../threads/thread-history-controller";
|
||||
import type { ThreadRenameController } from "../threads/thread-rename-controller";
|
||||
import type { ChatThreadActions } from "../threads/actions";
|
||||
import type { GoalActions } from "../threads/goal-actions";
|
||||
import type { HistoryController } from "../threads/history-controller";
|
||||
import type { ChatInboundController } from "../protocol/inbound/controller";
|
||||
import { currentModel, runtimeConfigOrDefault } from "../runtime/effective";
|
||||
import type { RuntimeSnapshot } from "../runtime/snapshot";
|
||||
|
|
@ -94,10 +93,9 @@ export function createConversationSurfaceControllerGroup(
|
|||
serverThreads: ChatServerThreadActions;
|
||||
runtimeSettings: ChatRuntimeSettingsActions;
|
||||
threadActions: ChatThreadActions;
|
||||
threadRename: ThreadRenameController;
|
||||
reconnectActions: ChatReconnectActions;
|
||||
goals: ChatThreadGoalActions;
|
||||
history: ThreadHistoryController;
|
||||
goals: GoalActions;
|
||||
history: HistoryController;
|
||||
},
|
||||
) {
|
||||
const { plugin, state, render, messages, composerView, runtime, thread, liveState, status, lifecycle, client, scroll } = context;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ApprovalAction, PendingApproval } from "../../protocol/requests/ap
|
|||
import type { ChatInboundController } from "../../protocol/inbound/controller";
|
||||
import { pendingRequestFocusSignature } from "./signatures";
|
||||
import { pendingRequestBlockSnapshot, type PendingRequestBlockSnapshot } from "./snapshot";
|
||||
import type { PendingRequestMessageActions } from "../../ui/pending-request-message";
|
||||
import type { PendingRequestBlockActions } from "../../ui/pending-request-block";
|
||||
import { answersForPendingUserInput, type PendingUserInput } from "../../protocol/requests/user-input";
|
||||
|
||||
export interface PendingRequestControllerHost {
|
||||
|
|
@ -16,7 +16,7 @@ export interface PendingRequestControllerHost {
|
|||
|
||||
export class PendingRequestController {
|
||||
private lastFocusSignature = "";
|
||||
private readonly messageActions: PendingRequestMessageActions = {
|
||||
private readonly blockActions: PendingRequestBlockActions = {
|
||||
resolveApproval: (approval, action) => {
|
||||
this.resolveApproval(approval, action);
|
||||
},
|
||||
|
|
@ -40,8 +40,8 @@ export class PendingRequestController {
|
|||
return pendingRequestBlockSnapshot(this.host.stateStore.getState());
|
||||
}
|
||||
|
||||
actions(): PendingRequestMessageActions {
|
||||
return this.messageActions;
|
||||
actions(): PendingRequestBlockActions {
|
||||
return this.blockActions;
|
||||
}
|
||||
|
||||
resolveApproval(approval: PendingApproval, action: ApprovalAction): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { PendingTurnStart } from "../../state/reducer";
|
||||
import type { DisplayFileMention, DisplayItem, MessageDisplayItem } from "../../display/types";
|
||||
import { fileMentionsFromInput, userMessageDisplayText } from "../../protocol/display-items";
|
||||
import { attachHookRunsToTurn } from "../../display/hooks";
|
||||
import { fileMentionsFromInput, userMessageDisplayText } from "../../display/items/user-message";
|
||||
import { attachHookRunsToTurn } from "../../state/transcript-updates";
|
||||
import type { CodexInput } from "../../../../app-server/request-input";
|
||||
|
||||
export interface LocalUserMessageParams {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { jsonPreview } from "../../../utils";
|
||||
import { jsonPreview } from "../../../../utils";
|
||||
|
||||
interface DetailRow {
|
||||
key: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DisplayDetailMetaRow, DisplayDetailSection } from "./types";
|
||||
import { jsonPreview, truncate } from "../../../utils";
|
||||
import type { DisplayDetailMetaRow, DisplayDetailSection } from "../types";
|
||||
import { jsonPreview, truncate } from "../../../../utils";
|
||||
|
||||
const TOOL_SUMMARY_LIMIT = 140;
|
||||
|
||||
|
|
@ -1,21 +1,14 @@
|
|||
import {
|
||||
chatTurnBusy,
|
||||
type ChatActiveThreadState,
|
||||
type ChatRuntimeState,
|
||||
type ChatTranscriptState,
|
||||
type ChatTurnState,
|
||||
} from "../state/reducer";
|
||||
import { isCompletedTurnOutcomeMessage } from "./predicates";
|
||||
import type { DisplayItem, MessageDisplayItem } from "./types";
|
||||
import { isCompletedTurnOutcomeMessage } from "./turn-outcome-message";
|
||||
|
||||
export interface ForkCandidate {
|
||||
itemId: string;
|
||||
displayItemId: string;
|
||||
turnId: string;
|
||||
}
|
||||
|
||||
export interface RollbackCandidate {
|
||||
turnId: string;
|
||||
itemId: string;
|
||||
displayItemId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
|
|
@ -23,13 +16,13 @@ export function forkCandidatesFromItems(items: readonly DisplayItem[]): readonly
|
|||
const turnOutcomeItemsByTurn = new Map<string, ForkCandidate>();
|
||||
for (const item of items) {
|
||||
if (!item.turnId || !isCompletedTurnOutcomeMessage(item)) continue;
|
||||
turnOutcomeItemsByTurn.set(item.turnId, { itemId: item.id, turnId: item.turnId });
|
||||
turnOutcomeItemsByTurn.set(item.turnId, { displayItemId: item.id, turnId: item.turnId });
|
||||
}
|
||||
return [...turnOutcomeItemsByTurn.values()];
|
||||
}
|
||||
|
||||
export function isForkCandidateItem(item: DisplayItem, candidates: readonly ForkCandidate[]): boolean {
|
||||
return candidates.some((candidate) => item.id === candidate.itemId && item.turnId === candidate.turnId);
|
||||
return candidates.some((candidate) => item.id === candidate.displayItemId && item.turnId === candidate.turnId);
|
||||
}
|
||||
|
||||
export function turnsAfterTurnId(items: readonly DisplayItem[], turnId: string): number | null {
|
||||
|
|
@ -47,28 +40,18 @@ export function rollbackCandidateFromItems(items: readonly DisplayItem[]): Rollb
|
|||
|
||||
return {
|
||||
turnId: lastTurnId,
|
||||
itemId: userMessage.id,
|
||||
displayItemId: userMessage.id,
|
||||
text: userMessage.text,
|
||||
};
|
||||
}
|
||||
|
||||
export function isRollbackCandidateItem(item: DisplayItem, candidate: RollbackCandidate | null): boolean {
|
||||
return Boolean(
|
||||
candidate && item.kind === "message" && item.role === "user" && item.id === candidate.itemId && item.turnId === candidate.turnId,
|
||||
);
|
||||
}
|
||||
|
||||
export function implementPlanCandidateFromState(state: {
|
||||
activeThread: Pick<ChatActiveThreadState, "id">;
|
||||
turn: ChatTurnState;
|
||||
runtime: Pick<ChatRuntimeState, "selectedCollaborationMode">;
|
||||
transcript: Pick<ChatTranscriptState, "displayItems">;
|
||||
}): DisplayItem | null {
|
||||
if (!state.activeThread.id || chatTurnBusy(state) || state.runtime.selectedCollaborationMode !== "plan") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
[...state.transcript.displayItems].reverse().find((item) => item.kind === "message" && item.messageKind === "proposedPlan") ?? null
|
||||
candidate &&
|
||||
item.kind === "message" &&
|
||||
item.role === "user" &&
|
||||
item.id === candidate.displayItemId &&
|
||||
item.turnId === candidate.turnId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import type { AgentDisplayItem, AgentRunSummary, AgentRunSummaryAgent, AgentStateDisplay, DisplayItem, ExecutionState } from "./types";
|
||||
import { definedProp, truncate } from "../../../utils";
|
||||
import type { AgentDisplayItem, AgentStateDisplay, ExecutionState } from "../types";
|
||||
import { definedProp } from "../../../../utils";
|
||||
|
||||
const ACTIVE_AGENT_PREVIEW_LIMIT = 96;
|
||||
type AgentRunState = "running" | "completed" | "failed";
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
||||
|
|
@ -51,7 +49,7 @@ export function agentDisplayItem(item: DisplayCollabAgentToolCall, turnId?: stri
|
|||
role: "tool",
|
||||
text: `${agentActivitySummaryLabel(item.tool)}\nstatus: ${item.status}${receiverText}${promptText}`,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
tool: item.tool,
|
||||
status: item.status,
|
||||
senderThreadId: item.senderThreadId,
|
||||
|
|
@ -73,73 +71,6 @@ export function agentActivitySummaryLabel(tool: string): string {
|
|||
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;
|
||||
|
||||
const agentStatuses = new Map<string, AgentStateDisplay>();
|
||||
for (const item of items) {
|
||||
if (item.kind !== "agent" || item.turnId !== activeTurnId) continue;
|
||||
if (item.agents.length > 0) {
|
||||
for (const agent of item.agents) {
|
||||
agentStatuses.set(agent.threadId, agent);
|
||||
}
|
||||
} else {
|
||||
for (const threadId of item.receiverThreadIds) {
|
||||
agentStatuses.set(threadId, { threadId, status: item.status, message: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (agentStatuses.size === 0) return null;
|
||||
|
||||
const summary = { running: 0, completed: 0, failed: 0, agents: [] as AgentRunSummaryAgent[], additionalAgents: 0 };
|
||||
const agents = [...agentStatuses.values()];
|
||||
for (const agent of agents) {
|
||||
const state = agentRunState(agent.status);
|
||||
summary[state] += 1;
|
||||
}
|
||||
|
||||
if (summary.running === 0 && summary.failed === 0) return null;
|
||||
|
||||
summary.agents = agents
|
||||
.filter((agent) => agentRunState(agent.status) === "running")
|
||||
.sort((a, b) => a.threadId.localeCompare(b.threadId))
|
||||
.map((agent) => ({
|
||||
threadId: agent.threadId,
|
||||
status: agent.status,
|
||||
messagePreview: agentMessagePreview(agent.message, ACTIVE_AGENT_PREVIEW_LIMIT),
|
||||
}));
|
||||
|
||||
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]) => ({
|
||||
|
|
@ -163,10 +94,6 @@ function collabAgentExecutionState(tool: string, status: string, receiverThreadI
|
|||
return null;
|
||||
}
|
||||
|
||||
function agentRunState(status: string): AgentRunState {
|
||||
return collabAgentStateExecutionState(status) ?? "running";
|
||||
}
|
||||
|
||||
export function collabAgentStateExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, AGENT_STATES);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ThreadGoal, ThreadGoalStatus } from "../../../app-server/thread-goal";
|
||||
import { truncate } from "../../../utils";
|
||||
import type { GoalDisplayItem } from "./types";
|
||||
import type { ThreadGoal, ThreadGoalStatus } from "../../../../app-server/thread-goal";
|
||||
import { truncate } from "../../../../utils";
|
||||
import type { GoalDisplayItem } from "../types";
|
||||
|
||||
const GOAL_SUMMARY_LIMIT = 140;
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { definedProp } from "../../../utils";
|
||||
import type { DisplayDetailMetaRow, DisplayDetailSection, DisplayItem, HookDisplayItem } from "./types";
|
||||
import { definedProp } from "../../../../utils";
|
||||
import type { DisplayDetailMetaRow, DisplayDetailSection, HookDisplayItem } from "../types";
|
||||
|
||||
interface DisplayHookRun {
|
||||
id: string;
|
||||
|
|
@ -28,7 +28,7 @@ export function hookRunDisplayItem(run: DisplayHookRun, turnId: string | null, s
|
|||
text: hookSummary(run.eventName, run.statusMessage),
|
||||
toolLabel: "hook",
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: displayId,
|
||||
sourceItemId: displayId,
|
||||
status,
|
||||
details,
|
||||
output: "",
|
||||
|
|
@ -44,31 +44,6 @@ function hookEventName(eventName: string | null | undefined): string {
|
|||
return trimmed && trimmed.length > 0 ? trimmed : "Hook";
|
||||
}
|
||||
|
||||
export function attachHookRunsToTurn(
|
||||
items: readonly DisplayItem[],
|
||||
turnId: string,
|
||||
hookItemIds: readonly string[],
|
||||
afterItemId?: string | null,
|
||||
): DisplayItem[] {
|
||||
const hookIdSet = new Set(hookItemIds);
|
||||
const attachedHooks = items.filter((item) => hookIdSet.has(item.id)).map((item) => ({ ...item, turnId }));
|
||||
if (attachedHooks.length === 0) return [...items];
|
||||
|
||||
const withoutAttachedHooks = items.filter((item) => !hookIdSet.has(item.id));
|
||||
const anchorItemId = afterItemId ?? lastUserMessageAnchorId(withoutAttachedHooks, turnId);
|
||||
if (!anchorItemId) return [...withoutAttachedHooks, ...attachedHooks];
|
||||
const insertAfterIndex = withoutAttachedHooks.findIndex((item) => item.id === anchorItemId);
|
||||
if (insertAfterIndex === -1) return [...withoutAttachedHooks, ...attachedHooks];
|
||||
return [...withoutAttachedHooks.slice(0, insertAfterIndex + 1), ...attachedHooks, ...withoutAttachedHooks.slice(insertAfterIndex + 1)];
|
||||
}
|
||||
|
||||
function lastUserMessageAnchorId(items: readonly DisplayItem[], turnId: string): string | null {
|
||||
const anchor = [...items]
|
||||
.reverse()
|
||||
.find((item) => item.kind === "message" && item.role === "user" && (!item.turnId || item.turnId === turnId));
|
||||
return anchor?.id ?? null;
|
||||
}
|
||||
|
||||
function hookSummary(eventName: string | null | undefined, statusMessage: string | null | undefined): string {
|
||||
const message = statusMessage?.trim();
|
||||
const event = hookEventName(eventName);
|
||||
6
src/features/chat/display/items/proposed-plan.ts
Normal file
6
src/features/chat/display/items/proposed-plan.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export function normalizeProposedPlanMarkdown(text: string): string {
|
||||
return text
|
||||
.replace(/^\s*<proposed_plan>\s*\n?/i, "")
|
||||
.replace(/\n?\s*<\/proposed_plan>\s*$/i, "")
|
||||
.trim();
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { permissionRows } from "./permission-rows";
|
||||
import type { DisplayItem, ExecutionState } from "./types";
|
||||
import { pathsRelativeToRoot } from "./paths";
|
||||
import { permissionRows } from "../details/permission-rows";
|
||||
import type { DisplayItem, ExecutionState } from "../types";
|
||||
import { pathsRelativeToRoot } from "../details/path-labels";
|
||||
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DisplayItem } from "./types";
|
||||
import type { DisplayDetailSection } from "./types";
|
||||
import type { DisplayItem } from "../types";
|
||||
import type { DisplayDetailSection } from "../types";
|
||||
|
||||
export function createSystemItem(id: string, text: string): DisplayItem {
|
||||
return {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DisplayItem, ExecutionState } from "./types";
|
||||
import type { DisplayItem, ExecutionState } from "../types";
|
||||
|
||||
type DisplayExecutionState = Exclude<ExecutionState, null>;
|
||||
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
|
||||
|
|
@ -16,26 +16,13 @@ export interface TaskPlanStep {
|
|||
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, "")
|
||||
.replace(/\n?\s*<\/proposed_plan>\s*$/i, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function planProgressDisplayItem(turnId: string, explanation: string | null, plan: readonly TaskPlanStep[]): DisplayItem {
|
||||
export function taskProgressDisplayItem(turnId: string, explanation: string | null, plan: readonly TaskPlanStep[]): DisplayItem {
|
||||
const trimmedExplanation = explanation?.trim();
|
||||
const lines = plan.map((step) => `${taskStatusMarker(step.status)} ${step.step}`);
|
||||
const lines = plan.map((step) => `${taskProgressTextMarker(step.status)} ${step.step}`);
|
||||
const body = [trimmedExplanation, ...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 {
|
||||
|
|
@ -44,7 +31,7 @@ export function planProgressDisplayItem(turnId: string, explanation: string | nu
|
|||
role: "tool",
|
||||
text: body.length > 0 ? body : "Plan updated",
|
||||
turnId,
|
||||
itemId: `plan-progress-${turnId}`,
|
||||
sourceItemId: `plan-progress-${turnId}`,
|
||||
explanation: trimmedExplanation !== undefined && trimmedExplanation.length > 0 ? trimmedExplanation : null,
|
||||
steps: plan.map((step) => ({ step: step.step, status: step.status })),
|
||||
status,
|
||||
|
|
@ -52,6 +39,12 @@ export function planProgressDisplayItem(turnId: string, explanation: string | nu
|
|||
};
|
||||
}
|
||||
|
||||
function taskProgressTextMarker(status: TaskStepStatus): string {
|
||||
if (status === "completed") return "[x]";
|
||||
if (status === "inProgress") return "[>]";
|
||||
return "[ ]";
|
||||
}
|
||||
|
||||
function executionStateFromStatus(status: string, states: ExecutionStateByStatus): ExecutionState {
|
||||
return states[status] ?? null;
|
||||
}
|
||||
109
src/features/chat/display/items/user-message.ts
Normal file
109
src/features/chat/display/items/user-message.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import type { CodexInput, CodexInputItem } from "../../../../app-server/request-input";
|
||||
import type { DisplayFileMention } from "../types";
|
||||
|
||||
type TextRange = [number, number];
|
||||
|
||||
export function fileMentionsFromInput(input: readonly CodexInputItem[]): DisplayFileMention[] {
|
||||
const seen = new Set<string>();
|
||||
const mentions: DisplayFileMention[] = [];
|
||||
for (const item of input) {
|
||||
if (item.type !== "mention" || seen.has(item.path)) continue;
|
||||
seen.add(item.path);
|
||||
mentions.push({ name: item.name, path: item.path });
|
||||
}
|
||||
return mentions;
|
||||
}
|
||||
|
||||
export function userMessageDisplayText(text: string, input: CodexInput): string {
|
||||
const names = resolvedSkillNames(input);
|
||||
if (names.length === 0) return text;
|
||||
|
||||
const pattern = new RegExp(`(^|[\\s([{])\\$(${names.map(escapeRegExp).join("|")})(?=$|[\\s\\])}.,;!?])`, "gi");
|
||||
const codeRanges = markdownCodeRanges(text);
|
||||
return text.replace(pattern, (match: string, prefix: string, name: string, offset: number) => {
|
||||
const dollarIndex = offset + prefix.length;
|
||||
return isIndexInRanges(dollarIndex, codeRanges) ? match : `${prefix}${markdownCodeSpan(`$${name}`)}`;
|
||||
});
|
||||
}
|
||||
|
||||
function resolvedSkillNames(input: readonly CodexInputItem[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const names: string[] = [];
|
||||
for (const item of input) {
|
||||
if (item.type !== "skill") continue;
|
||||
const key = item.name.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
names.push(item.name);
|
||||
}
|
||||
return names.sort((a, b) => b.length - a.length);
|
||||
}
|
||||
|
||||
function markdownCodeSpan(text: string): string {
|
||||
if (!text.includes("`")) return `\`${text}\``;
|
||||
const longestRun = Math.max(...Array.from(text.matchAll(/`+/g), (match) => match[0].length));
|
||||
const delimiter = "`".repeat(longestRun + 1);
|
||||
return `${delimiter} ${text} ${delimiter}`;
|
||||
}
|
||||
|
||||
function markdownCodeRanges(text: string): TextRange[] {
|
||||
return [...markdownFenceRanges(text), ...markdownInlineCodeRanges(text)].sort((a, b) => a[0] - b[0]);
|
||||
}
|
||||
|
||||
function markdownFenceRanges(text: string): TextRange[] {
|
||||
const ranges: TextRange[] = [];
|
||||
let active: { marker: string; start: number } | null = null;
|
||||
let offset = 0;
|
||||
for (const line of text.matchAll(/[^\n]*(?:\n|$)/g)) {
|
||||
const value = line[0];
|
||||
if (value.length === 0) break;
|
||||
const fence = /^(?: {0,3})(`{3,}|~{3,})/.exec(value);
|
||||
if (fence) {
|
||||
const marker = fence[1];
|
||||
if (!marker) continue;
|
||||
if (!active) {
|
||||
active = { marker, start: offset };
|
||||
} else if (marker.startsWith(active.marker.charAt(0)) && marker.length >= active.marker.length) {
|
||||
ranges.push([active.start, offset + value.length]);
|
||||
active = null;
|
||||
}
|
||||
}
|
||||
offset += value.length;
|
||||
}
|
||||
if (active) ranges.push([active.start, text.length]);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function markdownInlineCodeRanges(text: string): TextRange[] {
|
||||
const ranges: TextRange[] = [];
|
||||
const fenceRanges = markdownFenceRanges(text);
|
||||
let index = 0;
|
||||
while (index < text.length) {
|
||||
if (isIndexInRanges(index, fenceRanges) || text[index] !== "`") {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const match = /`+/.exec(text.slice(index));
|
||||
if (!match) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const delimiter = match[0];
|
||||
const end = text.indexOf(delimiter, index + delimiter.length);
|
||||
if (end < 0) {
|
||||
index += delimiter.length;
|
||||
continue;
|
||||
}
|
||||
ranges.push([index, end + delimiter.length]);
|
||||
index = end + delimiter.length;
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function isIndexInRanges(index: number, ranges: readonly TextRange[]): boolean {
|
||||
return ranges.some(([start, end]) => index >= start && index < end);
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { DIAGNOSTIC_PROBE_METHODS, appServerIdentity, appServerPlatform } from "../../../app-server/diagnostics";
|
||||
import { CLIENT_VERSION } from "../../../constants";
|
||||
import { DIAGNOSTIC_PROBE_METHODS, appServerIdentity, appServerPlatform } from "../../../../app-server/diagnostics";
|
||||
import { CLIENT_VERSION } from "../../../../constants";
|
||||
import type {
|
||||
Diagnostics,
|
||||
InitializeDiagnostics,
|
||||
DiagnosticProbeResult,
|
||||
McpServerDiagnostic,
|
||||
McpServerStatusSummary,
|
||||
} from "../../../app-server/diagnostics";
|
||||
} from "../../../../app-server/diagnostics";
|
||||
|
||||
interface DiagnosticRow {
|
||||
label: string;
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
import type { RateLimitWindow, SpendControlLimitSnapshot, ThreadTokenUsage } from "../../../app-server/runtime-metrics";
|
||||
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 type { RateLimitWindow, SpendControlLimitSnapshot, ThreadTokenUsage } from "../../../../app-server/runtime-metrics";
|
||||
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 {
|
||||
currentApprovalsReviewer,
|
||||
currentApprovalPolicy,
|
||||
|
|
@ -14,9 +13,9 @@ import {
|
|||
runtimeConfigOrDefault,
|
||||
serviceTierLabel,
|
||||
supportedReasoningEfforts,
|
||||
} from "../runtime/effective";
|
||||
import type { RuntimeSnapshot } from "../runtime/snapshot";
|
||||
import { collaborationModeLabel, pendingRuntimeSettingLabel } from "../runtime/settings";
|
||||
} from "../../runtime/effective";
|
||||
import type { RuntimeSnapshot } from "../../runtime/snapshot";
|
||||
import { collaborationModeLabel, pendingRuntimeSettingLabel } from "../../runtime/settings";
|
||||
|
||||
export interface ContextSummary {
|
||||
label: string;
|
||||
|
|
@ -47,21 +46,21 @@ export interface RuntimeConfigSection {
|
|||
}
|
||||
|
||||
export interface StatusSummaryLinesInput {
|
||||
activeThreadId: ChatState["activeThread"]["id"];
|
||||
activeThreadId: RuntimeSnapshot["activeThreadId"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
nowMs: number;
|
||||
}
|
||||
|
||||
export interface ModelStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedModel: ChatState["runtime"]["requestedModel"];
|
||||
runtimeConfig: RuntimeSnapshot["runtimeConfig"];
|
||||
requestedModel: RuntimeSnapshot["requestedModel"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
collaborationModeLabel: string;
|
||||
}
|
||||
|
||||
export interface EffortStatusLinesInput {
|
||||
runtimeConfig: ChatState["connection"]["runtimeConfig"];
|
||||
requestedReasoningEffort: ChatState["runtime"]["requestedReasoningEffort"];
|
||||
runtimeConfig: RuntimeSnapshot["runtimeConfig"];
|
||||
requestedReasoningEffort: RuntimeSnapshot["requestedReasoningEffort"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
}
|
||||
|
||||
77
src/features/chat/display/stream/agent-summary.ts
Normal file
77
src/features/chat/display/stream/agent-summary.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { truncate } from "../../../../utils";
|
||||
import { collabAgentStateExecutionState } from "../items/agent";
|
||||
import type { AgentRunSummary, AgentRunSummaryAgent, AgentStateDisplay, DisplayItem } from "../types";
|
||||
|
||||
const ACTIVE_AGENT_PREVIEW_LIMIT = 96;
|
||||
type AgentRunState = "running" | "completed" | "failed";
|
||||
|
||||
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;
|
||||
|
||||
const agentStatuses = new Map<string, AgentStateDisplay>();
|
||||
for (const item of items) {
|
||||
if (item.kind !== "agent" || item.turnId !== activeTurnId) continue;
|
||||
if (item.agents.length > 0) {
|
||||
for (const agent of item.agents) {
|
||||
agentStatuses.set(agent.threadId, agent);
|
||||
}
|
||||
} else {
|
||||
for (const threadId of item.receiverThreadIds) {
|
||||
agentStatuses.set(threadId, { threadId, status: item.status, message: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (agentStatuses.size === 0) return null;
|
||||
|
||||
const summary = { running: 0, completed: 0, failed: 0, agents: [] as AgentRunSummaryAgent[], additionalAgents: 0 };
|
||||
const agents = [...agentStatuses.values()];
|
||||
for (const agent of agents) {
|
||||
const state = agentRunState(agent.status);
|
||||
summary[state] += 1;
|
||||
}
|
||||
|
||||
if (summary.running === 0 && summary.failed === 0) return null;
|
||||
|
||||
summary.agents = agents
|
||||
.filter((agent) => agentRunState(agent.status) === "running")
|
||||
.sort((a, b) => a.threadId.localeCompare(b.threadId))
|
||||
.map((agent) => ({
|
||||
threadId: agent.threadId,
|
||||
status: agent.status,
|
||||
messagePreview: agentMessagePreview(agent.message, ACTIVE_AGENT_PREVIEW_LIMIT),
|
||||
}));
|
||||
|
||||
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 agentRunState(status: string): AgentRunState {
|
||||
return collabAgentStateExecutionState(status) ?? "running";
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DisplayBlock, DisplayItem, DisplayKind } from "./types";
|
||||
import { isCompletedTurnOutcomeMessage } from "./turn-outcome-message";
|
||||
import { pathRelativeToRoot } from "./paths";
|
||||
import type { DisplayBlock, DisplayItem, DisplayKind } from "../types";
|
||||
import { isCompletedTurnOutcomeMessage } from "../predicates";
|
||||
import { pathRelativeToRoot } from "../details/path-labels";
|
||||
|
||||
const STEERING_ACTIVITY_LABEL = "steer";
|
||||
const STEERING_ACTIVITY_KIND = "userSteered";
|
||||
|
|
@ -79,7 +79,7 @@ function steeringActivityItem(item: DisplayItem, turnId: string): DisplayItem {
|
|||
role: "tool",
|
||||
text: item.text,
|
||||
turnId,
|
||||
...(item.itemId ? { itemId: item.itemId } : {}),
|
||||
...(item.sourceItemId ? { sourceItemId: item.sourceItemId } : {}),
|
||||
activityKind: STEERING_ACTIVITY_KIND,
|
||||
toolLabel: STEERING_ACTIVITY_LABEL,
|
||||
};
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import type { DisplayDetailSection, DisplayFileChange, DisplayFileMention, DisplayItem, ExecutionState } from "../display/types";
|
||||
import type { CodexInput, CodexInputItem } from "../../../app-server/request-input";
|
||||
import type { DisplayDetailSection, DisplayFileChange, DisplayItem, ExecutionState } from "./types";
|
||||
import type { AppServerFileUpdateChange, AppServerThreadItem, AppServerTurn } from "../../../app-server/turn-model";
|
||||
import { definedProp, truncate } from "../../../utils";
|
||||
import { referencedThreadDisplayFromPrompt } from "../../../domain/threads/reference";
|
||||
import { appServerUserItemText } from "../../../app-server/turn-model";
|
||||
import { agentDisplayItem } from "../display/agent";
|
||||
import { pathRelativeToRoot } from "../display/paths";
|
||||
import { normalizeProposedPlanMarkdown } from "../display/plan";
|
||||
import { agentDisplayItem } from "./items/agent";
|
||||
import { pathRelativeToRoot } from "./details/path-labels";
|
||||
import { normalizeProposedPlanMarkdown } from "./items/proposed-plan";
|
||||
import { fileMentionsFromInput, userMessageDisplayText } from "./items/user-message";
|
||||
import {
|
||||
bodyDetail,
|
||||
compactToolSummary,
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
jsonTargetLabel,
|
||||
metaDetail,
|
||||
statusQualifier,
|
||||
} from "../display/tool-format";
|
||||
} from "./details/tool-details";
|
||||
|
||||
type UserMessageItem = Extract<AppServerThreadItem, { type: "userMessage" }>;
|
||||
type AgentMessageItem = Extract<AppServerThreadItem, { type: "agentMessage" }>;
|
||||
|
|
@ -34,7 +34,6 @@ type ReviewModeItem =
|
|||
| Extract<AppServerThreadItem, { type: "enteredReviewMode" }>
|
||||
| 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>>;
|
||||
|
||||
|
|
@ -124,7 +123,7 @@ function userMessageDisplayItem(item: UserMessageItem, turnId?: string): Display
|
|||
referencedThread: referencedThread.reference,
|
||||
...definedProp("turnId", turnId),
|
||||
...definedProp("clientId", item.clientId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
...(mentionedFiles.length > 0 ? { mentionedFiles } : {}),
|
||||
};
|
||||
}
|
||||
|
|
@ -137,116 +136,11 @@ function userMessageDisplayItem(item: UserMessageItem, turnId?: string): Display
|
|||
copyText: text,
|
||||
...definedProp("turnId", turnId),
|
||||
...definedProp("clientId", item.clientId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
...(mentionedFiles.length > 0 ? { mentionedFiles } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function fileMentionsFromInput(input: readonly CodexInputItem[]): DisplayFileMention[] {
|
||||
const seen = new Set<string>();
|
||||
const mentions: DisplayFileMention[] = [];
|
||||
for (const item of input) {
|
||||
if (item.type !== "mention" || seen.has(item.path)) continue;
|
||||
seen.add(item.path);
|
||||
mentions.push({ name: item.name, path: item.path });
|
||||
}
|
||||
return mentions;
|
||||
}
|
||||
|
||||
export function userMessageDisplayText(text: string, input: CodexInput): string {
|
||||
const names = resolvedSkillNames(input);
|
||||
if (names.length === 0) return text;
|
||||
|
||||
const pattern = new RegExp(`(^|[\\s([{])\\$(${names.map(escapeRegExp).join("|")})(?=$|[\\s\\])}.,;!?])`, "gi");
|
||||
const codeRanges = markdownCodeRanges(text);
|
||||
return text.replace(pattern, (match: string, prefix: string, name: string, offset: number) => {
|
||||
const dollarIndex = offset + prefix.length;
|
||||
return isIndexInRanges(dollarIndex, codeRanges) ? match : `${prefix}${markdownCodeSpan(`$${name}`)}`;
|
||||
});
|
||||
}
|
||||
|
||||
function resolvedSkillNames(input: readonly CodexInputItem[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const names: string[] = [];
|
||||
for (const item of input) {
|
||||
if (item.type !== "skill") continue;
|
||||
const key = item.name.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
names.push(item.name);
|
||||
}
|
||||
return names.sort((a, b) => b.length - a.length);
|
||||
}
|
||||
|
||||
function markdownCodeSpan(text: string): string {
|
||||
if (!text.includes("`")) return `\`${text}\``;
|
||||
const longestRun = Math.max(...Array.from(text.matchAll(/`+/g), (match) => match[0].length));
|
||||
const delimiter = "`".repeat(longestRun + 1);
|
||||
return `${delimiter} ${text} ${delimiter}`;
|
||||
}
|
||||
|
||||
function markdownCodeRanges(text: string): TextRange[] {
|
||||
return [...markdownFenceRanges(text), ...markdownInlineCodeRanges(text)].sort((a, b) => a[0] - b[0]);
|
||||
}
|
||||
|
||||
function markdownFenceRanges(text: string): TextRange[] {
|
||||
const ranges: TextRange[] = [];
|
||||
let active: { marker: string; start: number } | null = null;
|
||||
let offset = 0;
|
||||
for (const line of text.matchAll(/[^\n]*(?:\n|$)/g)) {
|
||||
const value = line[0];
|
||||
if (value.length === 0) break;
|
||||
const fence = /^(?: {0,3})(`{3,}|~{3,})/.exec(value);
|
||||
if (fence) {
|
||||
const marker = fence[1];
|
||||
if (!marker) continue;
|
||||
if (!active) {
|
||||
active = { marker, start: offset };
|
||||
} else if (marker.startsWith(active.marker.charAt(0)) && marker.length >= active.marker.length) {
|
||||
ranges.push([active.start, offset + value.length]);
|
||||
active = null;
|
||||
}
|
||||
}
|
||||
offset += value.length;
|
||||
}
|
||||
if (active) ranges.push([active.start, text.length]);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function markdownInlineCodeRanges(text: string): TextRange[] {
|
||||
const ranges: TextRange[] = [];
|
||||
const fenceRanges = markdownFenceRanges(text);
|
||||
let index = 0;
|
||||
while (index < text.length) {
|
||||
if (isIndexInRanges(index, fenceRanges) || text[index] !== "`") {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const match = /`+/.exec(text.slice(index));
|
||||
if (!match) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const delimiter = match[0];
|
||||
const end = text.indexOf(delimiter, index + delimiter.length);
|
||||
if (end < 0) {
|
||||
index += delimiter.length;
|
||||
continue;
|
||||
}
|
||||
ranges.push([index, end + delimiter.length]);
|
||||
index = end + delimiter.length;
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function isIndexInRanges(index: number, ranges: readonly TextRange[]): boolean {
|
||||
return ranges.some(([start, end]) => index >= start && index < end);
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
||||
}
|
||||
|
||||
function agentMessageDisplayItem(item: AgentMessageItem, turnId?: string): DisplayItem {
|
||||
return {
|
||||
id: item.id,
|
||||
|
|
@ -256,7 +150,7 @@ function agentMessageDisplayItem(item: AgentMessageItem, turnId?: string): Displ
|
|||
text: item.text,
|
||||
copyText: item.text,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
messageState: "completed",
|
||||
};
|
||||
}
|
||||
|
|
@ -271,7 +165,7 @@ function planDisplayItem(item: PlanItem, turnId?: string): DisplayItem {
|
|||
text,
|
||||
copyText: text,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
messageState: "completed",
|
||||
};
|
||||
}
|
||||
|
|
@ -283,7 +177,7 @@ function hookPromptDisplayItem(item: HookPromptItem, turnId?: string): DisplayIt
|
|||
role: "tool",
|
||||
text: item.fragments.map((fragment) => fragment.text).join("\n\n") || "Hook prompt",
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +188,7 @@ function reasoningDisplayItem(item: ReasoningItem, turnId?: string): DisplayItem
|
|||
role: "tool",
|
||||
text: reasoningText(item),
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +203,7 @@ function mcpToolCallDisplayItem(item: McpToolCallItem, turnId?: string): Display
|
|||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
|
|
@ -332,7 +226,7 @@ function dynamicToolCallDisplayItem(item: DynamicToolCallItem, turnId?: string):
|
|||
text: compactToolSummary(null, target, statusQualifier(item.status, failure)),
|
||||
toolLabel: name,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
status: item.status,
|
||||
details: jsonDetails([
|
||||
["Arguments JSON", item.arguments],
|
||||
|
|
@ -351,7 +245,7 @@ function webSearchDisplayItem(item: WebSearchItem, turnId?: string): DisplayItem
|
|||
text: webSearchSummary(item),
|
||||
toolLabel: "web search",
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
details: webSearchDetails(item),
|
||||
output: "",
|
||||
};
|
||||
|
|
@ -366,7 +260,7 @@ function imageViewDisplayItem(item: ImageViewItem, turnId?: string): DisplayItem
|
|||
toolLabel: "imageView",
|
||||
summaryPath: true,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +274,7 @@ function imageGenerationDisplayItem(item: ImageGenerationItem, turnId?: string):
|
|||
toolLabel: "imageGeneration",
|
||||
summaryPath: Boolean(item.savedPath),
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
status: item.status,
|
||||
details: [
|
||||
...bodyDetail("Saved path", item.savedPath),
|
||||
|
|
@ -400,7 +294,7 @@ function reviewModeDisplayItem(item: ReviewModeItem, turnId?: string): DisplayIt
|
|||
text: item.type === "enteredReviewMode" ? "Entered review mode" : "Exited review mode",
|
||||
toolLabel: item.type,
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
output: item.review,
|
||||
};
|
||||
}
|
||||
|
|
@ -412,7 +306,7 @@ function contextCompactionDisplayItem(item: ContextCompactionItem, turnId?: stri
|
|||
role: "tool",
|
||||
text: "Context compaction",
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -571,7 +465,7 @@ function commandDisplayItem(item: CommandExecutionItem, turnId?: string): Displa
|
|||
actionLabel: commandActionLabel(item),
|
||||
text: compactToolSummary(null, target, qualifier),
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
command: item.command,
|
||||
cwd: item.cwd,
|
||||
status: item.status,
|
||||
|
|
@ -591,7 +485,7 @@ function fileChangeDisplayItem(item: FileChangeItem, turnId?: string): DisplayIt
|
|||
role: "tool",
|
||||
text: compactToolSummary(null, fileChangeTargetLabel(changes), qualifier),
|
||||
...definedProp("turnId", turnId),
|
||||
itemId: item.id,
|
||||
sourceItemId: item.id,
|
||||
status: item.status,
|
||||
changes,
|
||||
executionState: patchApplyExecutionState(item.status),
|
||||
|
|
@ -25,7 +25,7 @@ interface DisplayBase {
|
|||
role: DisplayRole;
|
||||
text: string;
|
||||
turnId?: string;
|
||||
itemId?: string;
|
||||
sourceItemId?: string;
|
||||
executionState?: ExecutionState;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { CodexPanelSettings } from "../../../settings/model";
|
|||
import type { ChatServerMetadataActions } from "../connection/server-actions/metadata";
|
||||
import type { ChatServerThreadActions } from "../connection/server-actions/threads";
|
||||
import type { ChatComposerController } from "../conversation/composer/controller";
|
||||
import type { ChatThreadActions } from "../threads/thread-actions";
|
||||
import type { ChatThreadActions } from "../threads/actions";
|
||||
import { scheduleAppServerWarmup } from "../connection/app-server-warmup";
|
||||
import { closeChatView, openChatView, type ChatViewLifecycleHost } from "./view-lifecycle";
|
||||
import { createToolbarArchiveConfirmState, ToolbarPanelController } from "./regions/toolbar";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
supportedReasoningEfforts,
|
||||
} from "../../runtime/effective";
|
||||
import { compactReasoningEffortLabel } from "../../conversation/turns/runtime-overrides";
|
||||
import { contextSummary } from "../../display/runtime-status";
|
||||
import { contextSummary } from "../../display/status/runtime";
|
||||
import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeSnapshot } from "../../runtime/snapshot";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { Thread } from "../../../../domain/threads/model";
|
||||
import { getThreadTitle } from "../../../../domain/threads/model";
|
||||
import type { ChatThreadActions } from "../../threads/thread-actions";
|
||||
import { runtimeConfigSections, rateLimitSummary } from "../../display/runtime-status";
|
||||
import { connectionDiagnosticSections } from "../../display/diagnostics";
|
||||
import type { ChatThreadActions } from "../../threads/actions";
|
||||
import { runtimeConfigSections, rateLimitSummary } from "../../display/status/runtime";
|
||||
import { connectionDiagnosticSections } from "../../display/status/diagnostics";
|
||||
import type { RuntimeSnapshot } from "../../runtime/snapshot";
|
||||
import type { ChatAction, ChatState, ChatStateStore } from "../../state/reducer";
|
||||
import type { ChatPanelShellState } from "../../ui/shell";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { McpServerStartupStatus } from "../../../../app-server/diagnostics"
|
|||
import type { ThreadConversationSummary } from "../../../../domain/threads/transcript";
|
||||
import { classifyAppServerLog } from "./app-server-logs";
|
||||
import { activeTurnId, type ChatAction, type ChatState, type ChatStateStore } from "../../state/reducer";
|
||||
import { createStructuredSystemItem, createSystemItem } from "../../display/system";
|
||||
import { createStructuredSystemItem, createSystemItem } from "../../display/items/system";
|
||||
import type { DisplayDetailSection } from "../../display/types";
|
||||
import { approvalResponse, type ApprovalAction, type PendingApproval } from "../requests/approval";
|
||||
import { userInputResponse, type PendingUserInput } from "../requests/user-input";
|
||||
|
|
@ -107,11 +107,11 @@ export class ChatInboundController {
|
|||
}
|
||||
|
||||
addSystemMessage(text: string): void {
|
||||
this.dispatch({ type: "transcript/system-message-added", item: createSystemItem(this.localItemId("system"), text) });
|
||||
this.dispatch({ type: "transcript/system-item-added", item: createSystemItem(this.localItemId("system"), text) });
|
||||
}
|
||||
|
||||
addStructuredSystemMessage(text: string, details: DisplayDetailSection[]): void {
|
||||
this.dispatch({ type: "transcript/system-message-added", item: createStructuredSystemItem(this.localItemId("system"), text, details) });
|
||||
this.dispatch({ type: "transcript/system-item-added", item: createStructuredSystemItem(this.localItemId("system"), text, details) });
|
||||
}
|
||||
|
||||
addDedupedSystemMessage(text: string): void {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { ServerNotification } from "../../../../app-server/types";
|
|||
import type { ThreadConversationSummary } from "../../../../domain/threads/transcript";
|
||||
import { jsonPreview } from "../../../../utils";
|
||||
import { activeTurnId, pendingTurnStart as pendingTurnStartForState, type ChatAction, type ChatState } from "../../state/reducer";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../../display/review";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../../display/items/review-result";
|
||||
import {
|
||||
appendAssistantDelta,
|
||||
appendItemOutput,
|
||||
|
|
@ -20,13 +20,14 @@ import {
|
|||
appendToolOutput,
|
||||
completeReasoningItems,
|
||||
upsertDisplayItem,
|
||||
} from "../../display/stream-updates";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns, normalizeFileChanges, shouldSuppressLifecycleItem } from "../display-items";
|
||||
import { planProgressDisplayItem } from "../../display/plan";
|
||||
import { createSystemItem } from "../../display/system";
|
||||
} from "../../state/transcript-updates";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns, normalizeFileChanges, shouldSuppressLifecycleItem } from "../../display/turn-items";
|
||||
import { taskProgressDisplayItem } from "../../display/items/task-progress";
|
||||
import { createSystemItem } from "../../display/items/system";
|
||||
import type { DisplayItem, DisplayKind, MessageDisplayItem } from "../../display/types";
|
||||
import { goalChangeItem } from "../../display/goal-messages";
|
||||
import { attachHookRunsToTurn, hookRunDisplayItem } from "../../display/hooks";
|
||||
import { goalChangeItem } from "../../display/items/goal";
|
||||
import { hookRunDisplayItem } from "../../display/items/hook-run";
|
||||
import { attachHookRunsToTurn } from "../../state/transcript-updates";
|
||||
import {
|
||||
routeServerNotification,
|
||||
type DiagnosticStatusNotificationMethod,
|
||||
|
|
@ -135,7 +136,7 @@ const STREAM_UPDATE_PLANNERS = {
|
|||
"turn/plan/updated": (_state, notification) =>
|
||||
actionPlan({
|
||||
type: "transcript/item-upserted",
|
||||
item: planProgressDisplayItem(notification.params.turnId, notification.params.explanation, notification.params.plan),
|
||||
item: taskProgressDisplayItem(notification.params.turnId, notification.params.explanation, notification.params.plan),
|
||||
}),
|
||||
"item/reasoning/summaryTextDelta": (state, notification) =>
|
||||
appendToolTextPlan(state, notification.params.itemId, notification.params.turnId, "reasoning", notification.params.delta, "reasoning"),
|
||||
|
|
@ -420,7 +421,7 @@ function fileChangePlan(itemId: string, turnId: string, changes: AppServerFileUp
|
|||
role: "tool",
|
||||
text: `File change ${status}`,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId: itemId,
|
||||
status,
|
||||
changes: normalizeFileChanges(changes),
|
||||
},
|
||||
|
|
@ -553,7 +554,7 @@ function isString(value: string | null | undefined): value is string {
|
|||
}
|
||||
|
||||
function systemMessagePlan(message: { id: string; text: string }): ChatNotificationPlan {
|
||||
return actionPlan({ type: "transcript/system-message-added", item: createSystemItem(message.id, message.text) });
|
||||
return actionPlan({ type: "transcript/system-item-added", item: createSystemItem(message.id, message.text) });
|
||||
}
|
||||
|
||||
function actionPlan(action: ChatAction): ChatNotificationPlan {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { jsonPreview } from "../../../../utils";
|
||||
import { permissionRows } from "../../display/permission-rows";
|
||||
import { permissionRows } from "../../display/details/permission-rows";
|
||||
import type { RequestId } from "../../../../app-server/types";
|
||||
|
||||
type SimpleApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel";
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {
|
|||
import type { RequestedServiceTier } from "../runtime/settings";
|
||||
import type { RequestId } from "../../../app-server/types";
|
||||
import type { ComposerSuggestion } from "../conversation/composer/suggestions";
|
||||
import { upsertDisplayItem } from "../display/stream-updates";
|
||||
import { upsertDisplayItem } from "./transcript-updates";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
import type {
|
||||
ActiveThreadResumedAction,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { activeTurnId as selectActiveTurnId, chatTurnBusy, pendingTurnStart } from "./reducer";
|
||||
import type { ChatState, PendingTurnStart } from "./reducer";
|
||||
import type { ChatActiveThreadState, ChatRuntimeState, ChatState, ChatTranscriptState, ChatTurnState, PendingTurnStart } from "./reducer";
|
||||
import type { Thread } from "../../../domain/threads/model";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
import { implementPlanCandidateFromState } from "../display/action-candidates";
|
||||
|
||||
export interface SubmissionStateSnapshot {
|
||||
activeThreadId: string | null;
|
||||
|
|
@ -43,3 +42,17 @@ export function submissionStateSnapshot(state: ChatState): SubmissionStateSnapsh
|
|||
export function canImplementPlan(state: ChatState, item: DisplayItem): boolean {
|
||||
return item.id === implementPlanCandidateFromState(state)?.id;
|
||||
}
|
||||
|
||||
export function implementPlanCandidateFromState(state: {
|
||||
activeThread: Pick<ChatActiveThreadState, "id">;
|
||||
turn: ChatTurnState;
|
||||
runtime: Pick<ChatRuntimeState, "selectedCollaborationMode">;
|
||||
transcript: Pick<ChatTranscriptState, "displayItems">;
|
||||
}): DisplayItem | null {
|
||||
if (!state.activeThread.id || chatTurnBusy(state) || state.runtime.selectedCollaborationMode !== "plan") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
[...state.transcript.displayItems].reverse().find((item) => item.kind === "message" && item.messageKind === "proposedPlan") ?? null
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { AssistantAuthoredMessageDisplayItem, DisplayFileChange, DisplayItem, DisplayKind } from "./types";
|
||||
import { isAssistantAuthoredMessage } from "./turn-outcome-message";
|
||||
import { normalizeProposedPlanMarkdown } from "./plan";
|
||||
import { normalizeProposedPlanMarkdown } from "../display/items/proposed-plan";
|
||||
import { isAssistantAuthoredMessage } from "../display/predicates";
|
||||
import type { AssistantAuthoredMessageDisplayItem, DisplayFileChange, DisplayItem, DisplayKind } from "../display/types";
|
||||
|
||||
export function upsertDisplayItem(items: readonly DisplayItem[], next: DisplayItem): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.id === next.id);
|
||||
|
|
@ -29,8 +29,8 @@ function mergeChanges(previous: DisplayItem, next: DisplayItem): DisplayFileChan
|
|||
return nextChanges && nextChanges.length > 0 ? nextChanges : previousChanges;
|
||||
}
|
||||
|
||||
export function appendAssistantDelta(items: readonly DisplayItem[], itemId: string, turnId: string, delta: string): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.itemId === itemId && item.kind === "message" && item.messageKind === "assistantResponse");
|
||||
export function appendAssistantDelta(items: readonly DisplayItem[], sourceItemId: string, turnId: string, delta: string): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.sourceItemId === sourceItemId && item.kind === "message" && item.messageKind === "assistantResponse");
|
||||
if (index !== -1) {
|
||||
return items.map((item, itemIndex) =>
|
||||
itemIndex === index && item.kind === "message" && item.messageKind === "assistantResponse"
|
||||
|
|
@ -47,14 +47,14 @@ export function appendAssistantDelta(items: readonly DisplayItem[], itemId: stri
|
|||
return [
|
||||
...items,
|
||||
{
|
||||
id: itemId,
|
||||
id: sourceItemId,
|
||||
kind: "message",
|
||||
messageKind: "assistantResponse",
|
||||
role: "assistant",
|
||||
text: delta,
|
||||
copyText: delta,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId,
|
||||
messageState: "streaming",
|
||||
},
|
||||
];
|
||||
|
|
@ -72,8 +72,8 @@ export function completeReasoningItems(items: readonly DisplayItem[], turnId: st
|
|||
);
|
||||
}
|
||||
|
||||
export function appendPlanDelta(items: readonly DisplayItem[], itemId: string, turnId: string, delta: string): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.itemId === itemId && isAssistantAuthoredMessage(item));
|
||||
export function appendPlanDelta(items: readonly DisplayItem[], sourceItemId: string, turnId: string, delta: string): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.sourceItemId === sourceItemId && isAssistantAuthoredMessage(item));
|
||||
if (index !== -1) {
|
||||
return items.map((item, itemIndex) =>
|
||||
itemIndex === index && isAssistantAuthoredMessage(item) ? appendPlanDeltaToMessage(item, turnId, delta) : item,
|
||||
|
|
@ -83,14 +83,14 @@ export function appendPlanDelta(items: readonly DisplayItem[], itemId: string, t
|
|||
return [
|
||||
...items,
|
||||
{
|
||||
id: itemId,
|
||||
id: sourceItemId,
|
||||
kind: "message",
|
||||
messageKind: "proposedPlan",
|
||||
role: "assistant",
|
||||
text,
|
||||
copyText: text,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId,
|
||||
messageState: "streaming",
|
||||
},
|
||||
];
|
||||
|
|
@ -110,37 +110,37 @@ function appendPlanDeltaToMessage(item: AssistantAuthoredMessageDisplayItem, tur
|
|||
|
||||
export function appendItemText(
|
||||
items: readonly DisplayItem[],
|
||||
itemId: string,
|
||||
sourceItemId: string,
|
||||
turnId: string,
|
||||
label: string,
|
||||
delta: string,
|
||||
kind: Extract<DisplayKind, "tool" | "hook" | "reasoning"> = "tool",
|
||||
): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.itemId === itemId);
|
||||
const index = items.findIndex((item) => item.sourceItemId === sourceItemId);
|
||||
if (index !== -1) {
|
||||
return items.map((item, itemIndex) => (itemIndex === index ? { ...item, text: `${item.text}${delta}` } : item));
|
||||
}
|
||||
return [
|
||||
...items,
|
||||
{
|
||||
id: itemId,
|
||||
id: sourceItemId,
|
||||
kind,
|
||||
role: "tool",
|
||||
text: `${label}: ${delta}`,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function appendToolOutput(
|
||||
items: readonly DisplayItem[],
|
||||
itemId: string,
|
||||
sourceItemId: string,
|
||||
turnId: string,
|
||||
delta: string,
|
||||
fallbackLabel: string,
|
||||
): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.itemId === itemId);
|
||||
const index = items.findIndex((item) => item.sourceItemId === sourceItemId);
|
||||
if (index !== -1) {
|
||||
return items.map((item, itemIndex) =>
|
||||
itemIndex === index && (item.kind === "tool" || item.kind === "hook" || item.kind === "reasoning")
|
||||
|
|
@ -151,13 +151,13 @@ export function appendToolOutput(
|
|||
return [
|
||||
...items,
|
||||
{
|
||||
id: itemId,
|
||||
id: sourceItemId,
|
||||
kind: "tool",
|
||||
role: "tool",
|
||||
text: "details",
|
||||
toolLabel: fallbackLabel,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId,
|
||||
output: delta,
|
||||
},
|
||||
];
|
||||
|
|
@ -165,13 +165,13 @@ export function appendToolOutput(
|
|||
|
||||
export function appendItemOutput(
|
||||
items: readonly DisplayItem[],
|
||||
itemId: string,
|
||||
sourceItemId: string,
|
||||
turnId: string,
|
||||
delta: string,
|
||||
kind: "command" | "fileChange",
|
||||
fallbackText: string,
|
||||
): DisplayItem[] {
|
||||
const index = items.findIndex((item) => item.itemId === itemId);
|
||||
const index = items.findIndex((item) => item.sourceItemId === sourceItemId);
|
||||
if (index !== -1) {
|
||||
return items.map((item, itemIndex) =>
|
||||
itemIndex === index && (item.kind === "command" || item.kind === "fileChange")
|
||||
|
|
@ -182,12 +182,12 @@ export function appendItemOutput(
|
|||
return [
|
||||
...items,
|
||||
{
|
||||
id: itemId,
|
||||
id: sourceItemId,
|
||||
kind,
|
||||
role: "tool",
|
||||
text: fallbackText,
|
||||
turnId,
|
||||
itemId,
|
||||
sourceItemId,
|
||||
output: delta,
|
||||
...(kind === "fileChange"
|
||||
? {
|
||||
|
|
@ -204,3 +204,28 @@ export function appendItemOutput(
|
|||
},
|
||||
] as DisplayItem[];
|
||||
}
|
||||
|
||||
export function attachHookRunsToTurn(
|
||||
items: readonly DisplayItem[],
|
||||
turnId: string,
|
||||
hookItemIds: readonly string[],
|
||||
afterItemId?: string | null,
|
||||
): DisplayItem[] {
|
||||
const hookIdSet = new Set(hookItemIds);
|
||||
const attachedHooks = items.filter((item) => hookIdSet.has(item.id)).map((item) => ({ ...item, turnId }));
|
||||
if (attachedHooks.length === 0) return [...items];
|
||||
|
||||
const withoutAttachedHooks = items.filter((item) => !hookIdSet.has(item.id));
|
||||
const anchorItemId = afterItemId ?? lastUserMessageAnchorId(withoutAttachedHooks, turnId);
|
||||
if (!anchorItemId) return [...withoutAttachedHooks, ...attachedHooks];
|
||||
const insertAfterIndex = withoutAttachedHooks.findIndex((item) => item.id === anchorItemId);
|
||||
if (insertAfterIndex === -1) return [...withoutAttachedHooks, ...attachedHooks];
|
||||
return [...withoutAttachedHooks.slice(0, insertAfterIndex + 1), ...attachedHooks, ...withoutAttachedHooks.slice(insertAfterIndex + 1)];
|
||||
}
|
||||
|
||||
function lastUserMessageAnchorId(items: readonly DisplayItem[], turnId: string): string | null {
|
||||
const anchor = [...items]
|
||||
.reverse()
|
||||
.find((item) => item.kind === "message" && item.role === "user" && (!item.turnId || item.turnId === turnId));
|
||||
return anchor?.id ?? null;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { upsertDisplayItem } from "../display/stream-updates";
|
||||
import { upsertDisplayItem } from "./transcript-updates";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
|
||||
export interface ChatTranscriptState {
|
||||
|
|
@ -11,7 +11,7 @@ export interface ChatTranscriptState {
|
|||
|
||||
export type TranscriptAction =
|
||||
| { type: "transcript/item-added"; item: DisplayItem }
|
||||
| { type: "transcript/system-message-added"; item: DisplayItem }
|
||||
| { type: "transcript/system-item-added"; item: DisplayItem }
|
||||
| { type: "transcript/deduped-log-added"; text: string; item: DisplayItem }
|
||||
| { type: "transcript/history-loading-set"; loading: boolean }
|
||||
| {
|
||||
|
|
@ -36,7 +36,7 @@ export function initialChatTranscriptState(): ChatTranscriptState {
|
|||
export function reduceTranscriptSlice(state: ChatTranscriptState, action: TranscriptAction): ChatTranscriptState {
|
||||
switch (action.type) {
|
||||
case "transcript/item-added":
|
||||
case "transcript/system-message-added":
|
||||
case "transcript/system-item-added":
|
||||
return patchObject(state, { displayItems: [...state.displayItems, action.item] });
|
||||
case "transcript/deduped-log-added":
|
||||
if (state.reportedLogs.has(action.text)) return state;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { archiveThread } from "./thread-archive-actions";
|
||||
import { compactThread } from "./thread-compact-actions";
|
||||
import type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
import { forkThread, forkThreadFromTurn } from "./thread-fork-actions";
|
||||
import { renameThread } from "./thread-rename-actions";
|
||||
import { rollbackThread } from "./thread-rollback-actions";
|
||||
import { archiveThread } from "./archive-actions";
|
||||
import { compactThread } from "./compact-actions";
|
||||
import type { ChatThreadActionsHost } from "./action-context";
|
||||
import { forkThread, forkThreadFromTurn } from "./fork-actions";
|
||||
import { renameThread } from "./rename-actions";
|
||||
import { rollbackThread } from "./rollback-actions";
|
||||
|
||||
export type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
export type { ChatThreadActionsHost } from "./action-context";
|
||||
|
||||
export interface ChatThreadActions {
|
||||
compactThread: (threadId: string) => Promise<void>;
|
||||
|
|
@ -4,8 +4,8 @@ import { threadFromAppServerThread } from "../../../app-server/thread-model";
|
|||
import { transcriptEntriesFromAppServerTurn } from "../../../app-server/turn-model";
|
||||
import { exportArchivedThreadMarkdown } from "../../../domain/threads/export";
|
||||
import { chatTurnBusy } from "../state/reducer";
|
||||
import type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
import { threadActionState } from "./thread-action-context";
|
||||
import type { ChatThreadActionsHost } from "./action-context";
|
||||
import { threadActionState } from "./action-context";
|
||||
|
||||
export async function archiveThread(
|
||||
host: ChatThreadActionsHost,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
import { threadActionState, threadActionStillTargetsOriginalPanel } from "./thread-action-context";
|
||||
import type { ChatThreadActionsHost } from "./action-context";
|
||||
import { threadActionState, threadActionStillTargetsOriginalPanel } from "./action-context";
|
||||
|
||||
export async function compactThread(host: ChatThreadActionsHost, threadId: string): Promise<void> {
|
||||
await host.ensureConnected();
|
||||
|
|
@ -2,14 +2,14 @@ import type { ConnectionManager } from "../../../app-server/connection-manager";
|
|||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import { recoverRolloutTokenUsage } from "../../../app-server/rollout-token-usage";
|
||||
import type { ArchiveExportAdapter } from "../../../domain/threads/export";
|
||||
import { createChatThreadActions } from "./thread-actions";
|
||||
import { createChatThreadGoalActions } from "./thread-goal-actions";
|
||||
import { ThreadHistoryController } from "./thread-history-controller";
|
||||
import { createThreadIdentitySync } from "./thread-identity-sync";
|
||||
import { ThreadRenameController } from "./thread-rename-controller";
|
||||
import { ThreadResumeController } from "./thread-resume-controller";
|
||||
import { createThreadSelectionActions } from "./thread-selection-actions";
|
||||
import { RestoredThreadController } from "./restored-thread-controller";
|
||||
import { createChatThreadActions } from "./actions";
|
||||
import { createGoalActions } from "./goal-actions";
|
||||
import { HistoryController } from "./history-controller";
|
||||
import { createIdentitySync } from "./identity-sync";
|
||||
import { RenameController } from "./rename-controller";
|
||||
import { ResumeController } from "./resume-controller";
|
||||
import { createSelectionActions } from "./selection-actions";
|
||||
import { RestorationController } from "./restoration-controller";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
|
|
@ -76,7 +76,7 @@ export function createThreadControllerGroup(
|
|||
const currentClient = client.getClient;
|
||||
const { deferredTasks, resumeWork } = lifecycle;
|
||||
|
||||
const threadRename = new ThreadRenameController({
|
||||
const rename = new RenameController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
|
|
@ -87,9 +87,9 @@ export function createThreadControllerGroup(
|
|||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
});
|
||||
const resetThreadTurnPresence = (hadTurns: boolean) => {
|
||||
threadRename.resetThreadTurnPresence(hadTurns);
|
||||
rename.resetThreadTurnPresence(hadTurns);
|
||||
};
|
||||
const history = new ThreadHistoryController({
|
||||
const history = new HistoryController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
render: render.now,
|
||||
|
|
@ -102,7 +102,7 @@ export function createThreadControllerGroup(
|
|||
resumeWork.invalidate();
|
||||
history.invalidate();
|
||||
};
|
||||
const threadActions = createChatThreadActions({
|
||||
const actions = createChatThreadActions({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
|
|
@ -125,7 +125,7 @@ export function createThreadControllerGroup(
|
|||
plugin.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
});
|
||||
const goals = createChatThreadGoalActions({
|
||||
const goals = createGoalActions({
|
||||
stateStore,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
|
|
@ -136,22 +136,22 @@ export function createThreadControllerGroup(
|
|||
render: render.now,
|
||||
refreshLiveState: liveState.refresh,
|
||||
});
|
||||
let threadResume: ThreadResumeController | null = null;
|
||||
const restoredThread = new RestoredThreadController({
|
||||
let resume: ResumeController | null = null;
|
||||
const restoration = new RestorationController({
|
||||
deferredTasks,
|
||||
opened: lifecycle.getOpened,
|
||||
resumeThread: (threadId) => requireThreadController(threadResume, "thread resume controller").resumeThread(threadId),
|
||||
resumeThread: (threadId) => requireThreadController(resume, "resume controller").resumeThread(threadId),
|
||||
invalidateResumeWork,
|
||||
stateStore,
|
||||
setStatus: status.set,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
});
|
||||
threadResume = new ThreadResumeController({
|
||||
resume = new ResumeController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
resumeWork,
|
||||
history,
|
||||
restoredThread,
|
||||
restoration,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
closing: lifecycle.getClosing,
|
||||
|
|
@ -168,9 +168,9 @@ export function createThreadControllerGroup(
|
|||
return response?.dataBase64 ?? "";
|
||||
}),
|
||||
});
|
||||
const threadIdentity = createThreadIdentitySync({
|
||||
const identity = createIdentitySync({
|
||||
stateStore,
|
||||
restoredThread,
|
||||
restoration,
|
||||
invalidateResumeWork,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
resetThreadTurnPresence,
|
||||
|
|
@ -182,12 +182,12 @@ export function createThreadControllerGroup(
|
|||
|
||||
return {
|
||||
history,
|
||||
threadActions,
|
||||
actions,
|
||||
goals,
|
||||
restoredThread,
|
||||
threadResume: requireThreadController(threadResume, "thread resume controller"),
|
||||
threadIdentity,
|
||||
threadRename,
|
||||
restoration,
|
||||
resume: requireThreadController(resume, "resume controller"),
|
||||
identity,
|
||||
rename,
|
||||
invalidateResumeWork,
|
||||
};
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ export function createThreadSelectionActionGroup(
|
|||
const { plugin, thread, status } = context;
|
||||
const stateStore = context.state.stateStore;
|
||||
|
||||
const threadSelection = createThreadSelectionActions({
|
||||
const selection = createSelectionActions({
|
||||
stateStore,
|
||||
closeForThreadSelection: () => {
|
||||
refs.closeForThreadSelection();
|
||||
|
|
@ -231,5 +231,5 @@ export function createThreadSelectionActionGroup(
|
|||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
|
||||
return { threadSelection };
|
||||
return { selection };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { inheritedForkThreadName } from "../../../domain/threads/model";
|
||||
import { chatTurnBusy } from "../state/reducer";
|
||||
import { turnsAfterTurnId } from "../display/action-candidates";
|
||||
import { archiveThreadOnServer } from "./thread-archive-actions";
|
||||
import type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
import { threadActionState, threadActionStillTargetsOriginalPanel } from "./thread-action-context";
|
||||
import { turnsAfterTurnId } from "../display/item-actions";
|
||||
import { archiveThreadOnServer } from "./archive-actions";
|
||||
import type { ChatThreadActionsHost } from "./action-context";
|
||||
import { threadActionState, threadActionStillTargetsOriginalPanel } from "./action-context";
|
||||
|
||||
export function forkThread(host: ChatThreadActionsHost, threadId: string): Promise<void> {
|
||||
return forkThreadFromTurn(host, threadId, null, false);
|
||||
|
|
@ -8,9 +8,9 @@ import {
|
|||
} from "../../../app-server/thread-goal";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { GoalDisplayItem } from "../display/types";
|
||||
import { goalChangeItem } from "../display/goal-messages";
|
||||
import { goalChangeItem } from "../display/items/goal";
|
||||
|
||||
export interface ChatThreadGoalActionsHost {
|
||||
export interface GoalActionsHost {
|
||||
stateStore: ChatStateStore;
|
||||
currentClient: () => AppServerClient | null;
|
||||
ensureConnected: () => Promise<void>;
|
||||
|
|
@ -20,7 +20,7 @@ export interface ChatThreadGoalActionsHost {
|
|||
refreshLiveState: () => void;
|
||||
}
|
||||
|
||||
export interface ChatThreadGoalActions {
|
||||
export interface GoalActions {
|
||||
activeGoal: () => ThreadGoal | null;
|
||||
syncThreadGoal: (threadId: string) => Promise<void>;
|
||||
setObjective: (threadId: string, objective: string, tokenBudget: number | null) => Promise<boolean>;
|
||||
|
|
@ -28,7 +28,7 @@ export interface ChatThreadGoalActions {
|
|||
clear: (threadId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export function createChatThreadGoalActions(host: ChatThreadGoalActionsHost): ChatThreadGoalActions {
|
||||
export function createGoalActions(host: GoalActionsHost): GoalActions {
|
||||
return {
|
||||
activeGoal: () => host.stateStore.getState().activeThread.goal,
|
||||
syncThreadGoal: (threadId) => syncThreadGoal(host, threadId),
|
||||
|
|
@ -38,7 +38,7 @@ export function createChatThreadGoalActions(host: ChatThreadGoalActionsHost): Ch
|
|||
};
|
||||
}
|
||||
|
||||
async function syncThreadGoal(host: ChatThreadGoalActionsHost, threadId: string): Promise<void> {
|
||||
async function syncThreadGoal(host: GoalActionsHost, threadId: string): Promise<void> {
|
||||
const client = host.currentClient();
|
||||
if (!client) return;
|
||||
try {
|
||||
|
|
@ -50,7 +50,7 @@ async function syncThreadGoal(host: ChatThreadGoalActionsHost, threadId: string)
|
|||
}
|
||||
|
||||
async function setObjective(
|
||||
host: ChatThreadGoalActionsHost,
|
||||
host: GoalActionsHost,
|
||||
threadId: string,
|
||||
objective: string,
|
||||
tokenBudget: number | null,
|
||||
|
|
@ -73,11 +73,11 @@ async function setObjective(
|
|||
return applied;
|
||||
}
|
||||
|
||||
function setGoalStatus(host: ChatThreadGoalActionsHost, threadId: string, status: ThreadGoalStatus): Promise<boolean> {
|
||||
function setGoalStatus(host: GoalActionsHost, threadId: string, status: ThreadGoalStatus): Promise<boolean> {
|
||||
return setGoal(host, threadId, { status });
|
||||
}
|
||||
|
||||
async function clearGoal(host: ChatThreadGoalActionsHost, threadId: string): Promise<boolean> {
|
||||
async function clearGoal(host: GoalActionsHost, threadId: string): Promise<boolean> {
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
if (!client) return false;
|
||||
|
|
@ -91,7 +91,7 @@ async function clearGoal(host: ChatThreadGoalActionsHost, threadId: string): Pro
|
|||
}
|
||||
}
|
||||
|
||||
async function setGoal(host: ChatThreadGoalActionsHost, threadId: string, params: ThreadGoalUpdate): Promise<boolean> {
|
||||
async function setGoal(host: GoalActionsHost, threadId: string, params: ThreadGoalUpdate): Promise<boolean> {
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
if (!client) return false;
|
||||
|
|
@ -105,7 +105,7 @@ async function setGoal(host: ChatThreadGoalActionsHost, threadId: string, params
|
|||
}
|
||||
|
||||
function applyGoalIfActive(
|
||||
host: ChatThreadGoalActionsHost,
|
||||
host: GoalActionsHost,
|
||||
threadId: string,
|
||||
goal: ThreadGoal | null,
|
||||
options: { reportChange: boolean },
|
||||
|
|
@ -120,7 +120,7 @@ function applyGoalIfActive(
|
|||
return true;
|
||||
}
|
||||
|
||||
async function recordGoalUserMessage(host: ChatThreadGoalActionsHost, threadId: string, objective: string): Promise<void> {
|
||||
async function recordGoalUserMessage(host: GoalActionsHost, threadId: string, objective: string): Promise<void> {
|
||||
const client = host.currentClient();
|
||||
if (!client) return;
|
||||
try {
|
||||
|
|
@ -130,7 +130,7 @@ async function recordGoalUserMessage(host: ChatThreadGoalActionsHost, threadId:
|
|||
}
|
||||
}
|
||||
|
||||
function addThreadScopedSystemMessage(host: ChatThreadGoalActionsHost, threadId: string, text: string): void {
|
||||
function addThreadScopedSystemMessage(host: GoalActionsHost, threadId: string, text: string): void {
|
||||
if (host.stateStore.getState().activeThread.id !== threadId) return;
|
||||
host.addSystemMessage(text);
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import type { ThreadTurnsPage } from "../../../app-server/turn-history";
|
||||
import type { ChatAction, ChatState, ChatStateStore } from "../state/reducer";
|
||||
import { displayItemsFromTurns } from "../protocol/display-items";
|
||||
import { displayItemsFromTurns } from "../display/turn-items";
|
||||
|
||||
export interface ThreadHistoryControllerHost {
|
||||
export interface HistoryControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
currentClient: () => AppServerClient | null;
|
||||
render: () => void;
|
||||
|
|
@ -20,10 +20,10 @@ type ThreadHistoryLoadLifecycleEvent =
|
|||
| { type: "finished"; load: ActiveThreadHistoryLoad }
|
||||
| { type: "invalidated" };
|
||||
|
||||
export class ThreadHistoryController {
|
||||
export class HistoryController {
|
||||
private lifecycle: ThreadHistoryLoadLifecycleState = { kind: "idle" };
|
||||
|
||||
constructor(private readonly host: ThreadHistoryControllerHost) {}
|
||||
constructor(private readonly host: HistoryControllerHost) {}
|
||||
|
||||
private get state(): ChatState {
|
||||
return this.host.stateStore.getState();
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import type { RestoredThreadController } from "./restored-thread-controller";
|
||||
import type { RestorationController } from "./restoration-controller";
|
||||
import { activeThreadId, listedThreads } from "../state/selectors";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
|
||||
export interface ThreadIdentitySyncHost {
|
||||
export interface IdentitySyncHost {
|
||||
stateStore: ChatStateStore;
|
||||
restoredThread: RestoredThreadController;
|
||||
restoration: RestorationController;
|
||||
invalidateResumeWork: () => void;
|
||||
clearDeferredRestoredThreadHydration: () => void;
|
||||
resetThreadTurnPresence: (hadTurns: boolean) => void;
|
||||
|
|
@ -14,13 +14,13 @@ export interface ThreadIdentitySyncHost {
|
|||
render: () => void;
|
||||
}
|
||||
|
||||
export interface ThreadIdentitySync {
|
||||
export interface IdentitySync {
|
||||
clearActiveThreadContext: () => void;
|
||||
notifyThreadArchived: (threadId: string) => void;
|
||||
notifyThreadRenamed: (threadId: string, name: string | null) => void;
|
||||
}
|
||||
|
||||
export function createThreadIdentitySync(host: ThreadIdentitySyncHost): ThreadIdentitySync {
|
||||
export function createIdentitySync(host: IdentitySyncHost): IdentitySync {
|
||||
return {
|
||||
clearActiveThreadContext: () => {
|
||||
clearActiveThreadContext(host);
|
||||
|
|
@ -34,9 +34,9 @@ export function createThreadIdentitySync(host: ThreadIdentitySyncHost): ThreadId
|
|||
};
|
||||
}
|
||||
|
||||
function clearActiveThreadContext(host: ThreadIdentitySyncHost): void {
|
||||
function clearActiveThreadContext(host: IdentitySyncHost): void {
|
||||
host.invalidateResumeWork();
|
||||
host.restoredThread.clear();
|
||||
host.restoration.clear();
|
||||
host.clearDeferredRestoredThreadHydration();
|
||||
host.stateStore.dispatch({ type: "active-thread/cleared" });
|
||||
host.resetThreadTurnPresence(false);
|
||||
|
|
@ -44,13 +44,13 @@ function clearActiveThreadContext(host: ThreadIdentitySyncHost): void {
|
|||
host.refreshLiveState();
|
||||
}
|
||||
|
||||
function notifyThreadArchived(host: ThreadIdentitySyncHost, threadId: string): void {
|
||||
function notifyThreadArchived(host: IdentitySyncHost, threadId: string): void {
|
||||
if (activeThreadId(host.stateStore.getState()) !== threadId) return;
|
||||
clearActiveThreadContext(host);
|
||||
host.render();
|
||||
}
|
||||
|
||||
function notifyThreadRenamed(host: ThreadIdentitySyncHost, threadId: string, name: string | null): void {
|
||||
function notifyThreadRenamed(host: IdentitySyncHost, threadId: string, name: string | null): void {
|
||||
let changed = false;
|
||||
const renamedThreads = listedThreads(host.stateStore.getState()).map((thread) => {
|
||||
if (thread.id !== threadId) return thread;
|
||||
|
|
@ -58,12 +58,12 @@ function notifyThreadRenamed(host: ThreadIdentitySyncHost, threadId: string, nam
|
|||
return { ...thread, name };
|
||||
});
|
||||
host.stateStore.dispatch({ type: "thread-list/applied", threads: renamedThreads });
|
||||
const restoredThread = host.restoredThread.placeholder();
|
||||
const restoredThread = host.restoration.placeholder();
|
||||
if (restoredThread?.threadId === threadId && (restoredThread.title !== name || restoredThread.explicitName !== name)) {
|
||||
host.restoredThread.rename(threadId, name);
|
||||
host.restoration.rename(threadId, name);
|
||||
changed = true;
|
||||
}
|
||||
const activeThreadChanged = activeThreadId(host.stateStore.getState()) === threadId || host.restoredThread.isPending(threadId);
|
||||
const activeThreadChanged = activeThreadId(host.stateStore.getState()) === threadId || host.restoration.isPending(threadId);
|
||||
if (!changed && !activeThreadChanged) return;
|
||||
if (activeThreadChanged) {
|
||||
host.notifyActiveThreadIdentityChanged();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ThreadNamingContext } from "../../../domain/threads/naming";
|
||||
import { truncate } from "../../../utils";
|
||||
import { isCompletedTurnOutcomeMessage } from "../display/turn-outcome-message";
|
||||
import { isCompletedTurnOutcomeMessage } from "../display/predicates";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
|
||||
const MAX_CONTEXT_CHARS = 4_000;
|
||||
|
|
@ -12,10 +12,10 @@ import type { CodexPanelSettings } from "../../../settings/model";
|
|||
import type { ChatAction, ChatState, ChatStateStore } from "../state/reducer";
|
||||
import { generateThreadTitleWithCodex } from "../../../app-server/thread-title-generation";
|
||||
import { completedConversationSummaryFromAppServerTurn } from "../../../app-server/turn-model";
|
||||
import { firstNamingContextFromDisplayItems, namingContextFromDisplayItems } from "./thread-naming";
|
||||
import { renameConnectedThread } from "./thread-rename-actions";
|
||||
import { firstNamingContextFromDisplayItems, namingContextFromDisplayItems } from "./naming";
|
||||
import { renameConnectedThread } from "./rename-actions";
|
||||
|
||||
export interface ThreadRenameEditState {
|
||||
export interface RenameEditState {
|
||||
draft: string;
|
||||
generating: boolean;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ type RenameLifecycleEvent =
|
|||
| { type: "generation-finished"; threadId: string; generatingState: RenameGeneratingState }
|
||||
| { type: "cleared" };
|
||||
|
||||
export interface ThreadRenameControllerHost {
|
||||
export interface RenameControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
vaultPath: string;
|
||||
settings: () => CodexPanelSettings;
|
||||
|
|
@ -46,7 +46,7 @@ export interface ThreadRenameControllerHost {
|
|||
generateThreadTitle?: (context: ThreadNamingContext) => Promise<string | null>;
|
||||
}
|
||||
|
||||
export class ThreadRenameController {
|
||||
export class RenameController {
|
||||
private activeThreadHadTurns = false;
|
||||
private readonly autoNameAttemptedThreadIds = new Set<string>();
|
||||
private readonly autoNameInFlightThreadIds = new Set<string>();
|
||||
|
|
@ -54,7 +54,7 @@ export class ThreadRenameController {
|
|||
private nextRenameGenerationId = 1;
|
||||
private renameState: RenameLifecycleState = { kind: "idle" };
|
||||
|
||||
constructor(private readonly host: ThreadRenameControllerHost) {}
|
||||
constructor(private readonly host: RenameControllerHost) {}
|
||||
|
||||
private get state(): ChatState {
|
||||
return this.host.stateStore.getState();
|
||||
|
|
@ -68,7 +68,7 @@ export class ThreadRenameController {
|
|||
this.activeThreadHadTurns = hadTurns;
|
||||
}
|
||||
|
||||
editState(threadId: string): ThreadRenameEditState | null {
|
||||
editState(threadId: string): RenameEditState | null {
|
||||
if (this.renameState.kind === "idle" || this.renameState.threadId !== threadId) return null;
|
||||
return {
|
||||
draft: this.renameState.draft,
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
type ChatViewDeferredTasks,
|
||||
} from "../lifecycle";
|
||||
|
||||
export interface RestoredThreadControllerHost {
|
||||
export interface RestorationControllerHost {
|
||||
deferredTasks: ChatViewDeferredTasks;
|
||||
opened: () => boolean;
|
||||
resumeThread: (threadId: string) => Promise<void>;
|
||||
|
|
@ -17,10 +17,10 @@ export interface RestoredThreadControllerHost {
|
|||
refreshTabHeader: () => void;
|
||||
}
|
||||
|
||||
export class RestoredThreadController {
|
||||
export class RestorationController {
|
||||
private lifecycle: RestoredThreadLifecycleState = { kind: "idle" };
|
||||
|
||||
constructor(private readonly host: RestoredThreadControllerHost) {}
|
||||
constructor(private readonly host: RestorationControllerHost) {}
|
||||
|
||||
placeholder(): RestoredThreadPlaceholderState | null {
|
||||
return this.lifecycle.kind === "placeholder" ? this.lifecycle : null;
|
||||
|
|
@ -2,17 +2,17 @@ import type { AppServerClient } from "../../../app-server/client";
|
|||
import type { ThreadTokenUsage } from "../../../app-server/runtime-metrics";
|
||||
import { activeThreadId, canSwitchToThread, displayItemsEmpty, listedThreads } from "../state/selectors";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { RestoredThreadController } from "./restored-thread-controller";
|
||||
import { resumedThreadActionFromAppServerResponse } from "./thread-resume";
|
||||
import type { ThreadHistoryController } from "./thread-history-controller";
|
||||
import type { RestorationController } from "./restoration-controller";
|
||||
import { resumedThreadActionFromAppServerResponse } from "./resume";
|
||||
import type { HistoryController } from "./history-controller";
|
||||
import type { ChatResumeWorkTracker, ActiveChatResume } from "../lifecycle";
|
||||
|
||||
export interface ThreadResumeControllerHost {
|
||||
export interface ResumeControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
vaultPath: string;
|
||||
resumeWork: ChatResumeWorkTracker;
|
||||
history: ThreadHistoryController;
|
||||
restoredThread: RestoredThreadController;
|
||||
history: HistoryController;
|
||||
restoration: RestorationController;
|
||||
currentClient: () => AppServerClient | null;
|
||||
ensureConnected: () => Promise<void>;
|
||||
closing: () => boolean;
|
||||
|
|
@ -26,8 +26,8 @@ export interface ThreadResumeControllerHost {
|
|||
recoverTokenUsageFromRollout?: (path: string) => Promise<ThreadTokenUsage | null>;
|
||||
}
|
||||
|
||||
export class ThreadResumeController {
|
||||
constructor(private readonly host: ThreadResumeControllerHost) {}
|
||||
export class ResumeController {
|
||||
constructor(private readonly host: ResumeControllerHost) {}
|
||||
|
||||
async resumeThread(threadId: string): Promise<void> {
|
||||
if (!canSwitchToThread(this.host.stateStore.getState(), threadId)) {
|
||||
|
|
@ -72,7 +72,7 @@ export class ThreadResumeController {
|
|||
listedThreads: listedThreads(this.host.stateStore.getState()),
|
||||
}),
|
||||
);
|
||||
this.host.restoredThread.clear();
|
||||
this.host.restoration.clear();
|
||||
this.host.clearDeferredRestoredThreadHydration();
|
||||
this.host.resetThreadTurnPresence(false);
|
||||
this.host.notifyActiveThreadIdentityChanged();
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { threadFromAppServerThread } from "../../../app-server/thread-model";
|
||||
import { rollbackCandidateFromItems } from "../display/action-candidates";
|
||||
import { displayItemsFromTurns } from "../protocol/display-items";
|
||||
import { rollbackCandidateFromItems } from "../display/item-actions";
|
||||
import { displayItemsFromTurns } from "../display/turn-items";
|
||||
import { chatTurnBusy } from "../state/reducer";
|
||||
import type { ChatThreadActionsHost } from "./thread-action-context";
|
||||
import { threadActionDispatch, threadActionState, threadActionStillTargetsPanel } from "./thread-action-context";
|
||||
import { resumedThreadActionFromActiveRuntime } from "./thread-resume";
|
||||
import type { ChatThreadActionsHost } from "./action-context";
|
||||
import { threadActionDispatch, threadActionState, threadActionStillTargetsPanel } from "./action-context";
|
||||
import { resumedThreadActionFromActiveRuntime } from "./resume";
|
||||
|
||||
export async function rollbackThread(host: ChatThreadActionsHost, threadId: string): Promise<void> {
|
||||
if (chatTurnBusy(threadActionState(host))) {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { canSwitchToThread } from "../state/selectors";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
|
||||
export interface ThreadSelectionActionsHost {
|
||||
export interface SelectionActionsHost {
|
||||
stateStore: ChatStateStore;
|
||||
closeForThreadSelection: () => void;
|
||||
focusThreadInOpenView: (threadId: string) => Promise<boolean>;
|
||||
|
|
@ -9,12 +9,12 @@ export interface ThreadSelectionActionsHost {
|
|||
addSystemMessage: (text: string) => void;
|
||||
}
|
||||
|
||||
export interface ThreadSelectionActions {
|
||||
export interface SelectionActions {
|
||||
selectThread(threadId: string): Promise<void>;
|
||||
selectThreadFromToolbar(threadId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export function createThreadSelectionActions(host: ThreadSelectionActionsHost): ThreadSelectionActions {
|
||||
export function createSelectionActions(host: SelectionActionsHost): SelectionActions {
|
||||
const selectThread = async (threadId: string): Promise<void> => {
|
||||
if (!canSwitchToThread(host.stateStore.getState(), threadId)) {
|
||||
host.addSystemMessage("Finish or interrupt the current turn before switching threads.");
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import type { ChatState } from "../../state/reducer";
|
||||
import { chatTurnBusy } from "../../state/reducer";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import { implementPlanCandidateFromState } from "../../state/selectors";
|
||||
import {
|
||||
forkCandidatesFromItems,
|
||||
implementPlanCandidateFromState,
|
||||
isForkCandidateItem,
|
||||
isRollbackCandidateItem,
|
||||
rollbackCandidateFromItems,
|
||||
} from "../../display/action-candidates";
|
||||
} from "../../display/item-actions";
|
||||
import type { MessageStreamContext } from "./context";
|
||||
import type { ChatMessageStreamContextPort } from "./ports";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ComponentChild as UiNode } from "preact";
|
|||
import type { ChatTurnLifecycleState } from "../../state/reducer";
|
||||
import type { PendingRequestBlockSnapshot } from "../../conversation/pending-requests/snapshot";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import type { PendingRequestMessageActions } from "../pending-request-message";
|
||||
import type { PendingRequestBlockActions } from "../pending-request-block";
|
||||
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
||||
|
||||
export interface MessageStreamBlock {
|
||||
|
|
@ -11,18 +11,18 @@ export interface MessageStreamBlock {
|
|||
node: UiNode;
|
||||
}
|
||||
|
||||
export type RenderableTextItem = Extract<DisplayItem, { kind: "message" | "system" | "userInputResult" }>;
|
||||
export type TextDisplayItem = Extract<DisplayItem, { kind: "message" | "system" | "userInputResult" }>;
|
||||
|
||||
export interface MessageDetailStateContext {
|
||||
export interface TextItemDetailStateContext {
|
||||
openDetails: ReadonlySet<string>;
|
||||
onDetailsToggle?: (key: string, open: boolean) => void;
|
||||
}
|
||||
|
||||
export interface MessageContentContext extends MessageDetailStateContext {
|
||||
export interface TextItemContentContext extends TextItemDetailStateContext {
|
||||
renderMarkdown: (parent: HTMLElement, text: string) => void;
|
||||
}
|
||||
|
||||
export interface MessageActionContext extends MessageDetailStateContext {
|
||||
export interface TextItemActionContext extends TextItemDetailStateContext {
|
||||
turnLifecycle: ChatTurnLifecycleState;
|
||||
copyText?: (text: string) => void;
|
||||
canImplementPlanItem?: (item: DisplayItem) => boolean;
|
||||
|
|
@ -33,7 +33,7 @@ export interface MessageActionContext extends MessageDetailStateContext {
|
|||
onForkItem?: (item: DisplayItem, archiveSource: boolean) => void;
|
||||
}
|
||||
|
||||
export interface MessageMetadataContext extends MessageDetailStateContext {
|
||||
export interface TextItemMetadataContext extends TextItemDetailStateContext {
|
||||
activeThreadId: string | null;
|
||||
workspaceRoot?: string | null;
|
||||
openTurnDiff?: (state: ChatTurnDiffViewState) => void;
|
||||
|
|
@ -51,13 +51,13 @@ interface MessageStreamLayoutContext {
|
|||
pendingRequests?: PendingRequestBlockContext;
|
||||
}
|
||||
|
||||
export interface MessageItemContext extends MessageContentContext, MessageActionContext, MessageMetadataContext {}
|
||||
export interface TextItemContext extends TextItemContentContext, TextItemActionContext, TextItemMetadataContext {}
|
||||
|
||||
export interface MessageStreamContext extends MessageStreamLayoutContext, MessageItemContext {}
|
||||
export interface MessageStreamContext extends MessageStreamLayoutContext, TextItemContext {}
|
||||
|
||||
export interface PendingRequestBlockContext {
|
||||
signature: string;
|
||||
snapshot: () => PendingRequestBlockSnapshot;
|
||||
actions: () => PendingRequestMessageActions;
|
||||
actions: () => PendingRequestBlockActions;
|
||||
consumeAutoFocus: () => boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { ChatAction, ChatState } from "../../state/reducer";
|
|||
import type { PendingRequestBlockSnapshot } from "../../conversation/pending-requests/snapshot";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
||||
import type { PendingRequestMessageActions } from "../pending-request-message";
|
||||
import type { PendingRequestBlockActions } from "../pending-request-block";
|
||||
|
||||
export interface ChatMessageStreamActionPort {
|
||||
rollbackThread: (threadId: string) => void;
|
||||
|
|
@ -14,7 +14,7 @@ export interface ChatMessageStreamActionPort {
|
|||
export interface ChatMessageStreamRequestPort {
|
||||
pendingSignature: () => string;
|
||||
pendingSnapshot: () => PendingRequestBlockSnapshot;
|
||||
pendingActions: () => PendingRequestMessageActions;
|
||||
pendingActions: () => PendingRequestBlockActions;
|
||||
consumePendingAutoFocus: () => boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ import { Fragment, type ComponentChild as UiNode } from "preact";
|
|||
import { useLayoutEffect, useState } from "preact/hooks";
|
||||
|
||||
import { activeTurnId } from "../../state/reducer";
|
||||
import { displayBlocksForItems } from "../../display/blocks";
|
||||
import type { ToolResultDisplayItem } from "../../display/tool-view";
|
||||
import { displayBlocksForItems } from "../../display/stream/blocks";
|
||||
import type { ToolResultDisplayItem } from "../tool-result-view";
|
||||
import type { DisplayBlock, DisplayItem } from "../../display/types";
|
||||
import { userInputDraftKey, userInputOtherDraftKey } from "../../protocol/requests/user-input";
|
||||
import { pendingRequestMessageNode } from "../pending-request-message";
|
||||
import { pendingRequestBlockNode } from "../pending-request-block";
|
||||
import { toolResultNode } from "../tool-result";
|
||||
import { activeAgentRunSummaryBlock, agentRunSummaryNode, workItemNode, type WorkItemDisplayItem } from "../work-items";
|
||||
import type { MessageStreamBlock, MessageStreamContext, RenderableTextItem } from "./context";
|
||||
import { messageItemNode } from "./message-item";
|
||||
import type { MessageStreamBlock, MessageStreamContext, TextDisplayItem } from "./context";
|
||||
import { textItemNode } from "./text-item";
|
||||
|
||||
function messageStreamActiveTurnId(context: Pick<MessageStreamContext, "turnLifecycle">): string | null {
|
||||
return activeTurnId({ lifecycle: context.turnLifecycle });
|
||||
}
|
||||
|
||||
function isRenderableTextItem(item: DisplayItem): item is RenderableTextItem {
|
||||
function isTextDisplayItem(item: DisplayItem): item is TextDisplayItem {
|
||||
return item.kind === "message" || item.kind === "system" || item.kind === "userInputResult";
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ function isRenderableWorkItem(item: DisplayItem): item is WorkItemDisplayItem {
|
|||
}
|
||||
|
||||
function displayItemNode(item: DisplayItem, context: MessageStreamContext): UiNode {
|
||||
if (isRenderableTextItem(item)) return messageItemNode(item, context);
|
||||
if (isTextDisplayItem(item)) return textItemNode(item, context);
|
||||
if (isRenderableToolResultItem(item)) return toolResultNode(item, context);
|
||||
if (isRenderableWorkItem(item)) return workItemNode(item, context);
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ function bottomLiveBlocks(context: MessageStreamContext, activeTurn: string | nu
|
|||
const snapshot = context.pendingRequests.snapshot();
|
||||
blocks.push({
|
||||
key: "pending-requests",
|
||||
node: pendingRequestMessageNode(
|
||||
node: pendingRequestBlockNode(
|
||||
snapshot.approvals,
|
||||
snapshot.pendingUserInputs,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { useEffect, useRef } from "preact/hooks";
|
|||
import { activeTurnId } from "../../state/reducer";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import { IconButton } from "../../../../shared/ui/components";
|
||||
import type { MessageActionContext, RenderableTextItem } from "./context";
|
||||
import type { TextItemActionContext, TextDisplayItem } from "./context";
|
||||
|
||||
export function MessageRole({ item, context }: { item: RenderableTextItem; context: MessageActionContext }): UiNode {
|
||||
export function TextItemHeader({ item, context }: { item: TextDisplayItem; context: TextItemActionContext }): UiNode {
|
||||
const forkActionsKey = `message:fork-actions:${item.id}`;
|
||||
const forkActionsOpen = context.openDetails.has(forkActionsKey);
|
||||
const roleRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -27,7 +27,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
|
||||
const copyAction =
|
||||
item.kind === "message" && context.copyText && isMessageCopyActionVisible(item, context) && !forkActionsOpen ? (
|
||||
<MessageAction
|
||||
<TextItemAction
|
||||
icon="copy"
|
||||
label="Copy message"
|
||||
className="codex-panel__copy-message"
|
||||
|
|
@ -39,7 +39,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
<div ref={roleRef} className={`codex-panel__message-role${forkActionsOpen ? " codex-panel__message-role--fork-open" : ""}`}>
|
||||
<span>{displayRoleLabel(item)}</span>
|
||||
{forkActionsOpen && context.canForkItem?.(item) ? (
|
||||
<MessageAction
|
||||
<TextItemAction
|
||||
icon="archive"
|
||||
label="Fork and archive"
|
||||
className="codex-panel__fork-and-archive-message"
|
||||
|
|
@ -52,7 +52,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
copyAction
|
||||
)}
|
||||
{context.canForkItem?.(item) ? (
|
||||
<MessageAction
|
||||
<TextItemAction
|
||||
icon="git-fork"
|
||||
label={forkActionsOpen ? "Fork" : "Fork from here"}
|
||||
className="codex-panel__fork-message"
|
||||
|
|
@ -67,7 +67,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
/>
|
||||
) : null}
|
||||
{context.canImplementPlanItem?.(item) ? (
|
||||
<MessageAction
|
||||
<TextItemAction
|
||||
icon="play"
|
||||
label="Implement plan"
|
||||
className="codex-panel__implement-plan"
|
||||
|
|
@ -75,7 +75,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
/>
|
||||
) : null}
|
||||
{context.canRollbackItem?.(item) ? (
|
||||
<MessageAction
|
||||
<TextItemAction
|
||||
icon="undo-2"
|
||||
label="Rollback last turn"
|
||||
className="codex-panel__rollback-turn"
|
||||
|
|
@ -86,7 +86,7 @@ export function MessageRole({ item, context }: { item: RenderableTextItem; conte
|
|||
);
|
||||
}
|
||||
|
||||
function MessageAction({
|
||||
function TextItemAction({
|
||||
icon,
|
||||
label,
|
||||
className,
|
||||
|
|
@ -120,7 +120,7 @@ function displayRoleLabel(item: DisplayItem): string {
|
|||
return "System";
|
||||
}
|
||||
|
||||
function isMessageCopyActionVisible(item: DisplayItem, context: Pick<MessageActionContext, "turnLifecycle">): boolean {
|
||||
function isMessageCopyActionVisible(item: DisplayItem, context: Pick<TextItemActionContext, "turnLifecycle">): boolean {
|
||||
if (item.kind !== "message" || item.copyText === undefined) return false;
|
||||
const activeTurn = activeTurnId({ lifecycle: context.turnLifecycle });
|
||||
return !(activeTurn && item.role === "assistant" && item.turnId === activeTurn);
|
||||
|
|
@ -2,7 +2,7 @@ import { Fragment, type ComponentChild as UiNode } from "preact";
|
|||
|
||||
import type { DisplayDetailSection, DisplayItem } from "../../display/types";
|
||||
import { IconButton } from "../../../../shared/ui/components";
|
||||
import type { MessageDetailStateContext, MessageMetadataContext } from "./context";
|
||||
import type { TextItemDetailStateContext, TextItemMetadataContext } from "./context";
|
||||
|
||||
export function ReferencedThread({ item }: { item: Extract<DisplayItem, { kind: "message" }> }): UiNode {
|
||||
const reference = item.referencedThread;
|
||||
|
|
@ -26,7 +26,7 @@ export function EditedFiles({
|
|||
context,
|
||||
}: {
|
||||
item: Extract<DisplayItem, { kind: "message" }>;
|
||||
context: MessageMetadataContext;
|
||||
context: TextItemMetadataContext;
|
||||
}): UiNode {
|
||||
const editedFiles = item.editedFiles ?? [];
|
||||
const label = editedFiles.length === 1 ? "Edited 1 file" : `Edited ${String(editedFiles.length)} files`;
|
||||
|
|
@ -76,7 +76,7 @@ export function MentionedFiles({
|
|||
context,
|
||||
}: {
|
||||
item: Extract<DisplayItem, { kind: "message" }>;
|
||||
context: MessageDetailStateContext;
|
||||
context: TextItemDetailStateContext;
|
||||
}): UiNode {
|
||||
const mentionedFiles = item.mentionedFiles ?? [];
|
||||
const label = mentionedFiles.length === 1 ? "Mentioned 1 file" : `Mentioned ${String(mentionedFiles.length)} files`;
|
||||
|
|
@ -115,14 +115,14 @@ export function AutoReviewSummaries({ summaries }: { summaries: string[] }): UiN
|
|||
);
|
||||
}
|
||||
|
||||
export function MessageDetails({
|
||||
itemId,
|
||||
export function TextItemDetails({
|
||||
displayItemId,
|
||||
details,
|
||||
context,
|
||||
}: {
|
||||
itemId: string;
|
||||
displayItemId: string;
|
||||
details: DisplayDetailSection[];
|
||||
context: MessageDetailStateContext;
|
||||
context: TextItemDetailStateContext;
|
||||
}): UiNode {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -130,7 +130,7 @@ export function MessageDetails({
|
|||
<RememberedDetails
|
||||
key={`${section.title ?? "Details"}:${String(index)}`}
|
||||
detailsClassName="codex-panel__output"
|
||||
detailsKey={`${itemId}:message-detail:${String(index)}`}
|
||||
detailsKey={`${displayItemId}:text-item-detail:${String(index)}`}
|
||||
summary={section.title ?? "Details"}
|
||||
context={context}
|
||||
>
|
||||
|
|
@ -184,7 +184,7 @@ function RememberedDetails({
|
|||
detailsClassName: string;
|
||||
detailsKey: string;
|
||||
summary: string;
|
||||
context: MessageDetailStateContext;
|
||||
context: TextItemDetailStateContext;
|
||||
children: UiNode;
|
||||
}): UiNode {
|
||||
const details = (
|
||||
|
|
@ -3,26 +3,26 @@ import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
|||
|
||||
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";
|
||||
import { AutoReviewSummaries, EditedFiles, MentionedFiles, MessageDetails, ReferencedThread, SystemDetails } from "./message-metadata";
|
||||
import type { TextItemContentContext, TextItemContext, TextDisplayItem } from "./context";
|
||||
import { TextItemHeader } from "./text-item-actions";
|
||||
import { AutoReviewSummaries, EditedFiles, MentionedFiles, TextItemDetails, ReferencedThread, SystemDetails } from "./text-item-metadata";
|
||||
|
||||
const USER_MESSAGE_COLLAPSE_HEIGHT_PX = 360;
|
||||
|
||||
export function messageItemNode(item: RenderableTextItem, context: MessageItemContext): UiNode {
|
||||
return <MessageItem item={item} context={context} />;
|
||||
export function textItemNode(item: TextDisplayItem, context: TextItemContext): UiNode {
|
||||
return <TextItem item={item} context={context} />;
|
||||
}
|
||||
|
||||
function MessageItem({ item, context }: { item: RenderableTextItem; context: MessageItemContext }): UiNode {
|
||||
function TextItem({ item, context }: { item: TextDisplayItem; context: TextItemContext }): UiNode {
|
||||
const collapsible = isCollapsibleUserMessage(item);
|
||||
const details = "details" in item ? item.details : undefined;
|
||||
return (
|
||||
<div className={`${messageClass(item)}${executionClassName(item.executionState ?? null)}`}>
|
||||
<MessageRole item={item} context={context} />
|
||||
<div className={`${textItemClass(item)}${executionClassName(item.executionState ?? null)}`}>
|
||||
<TextItemHeader item={item} context={context} />
|
||||
{collapsible ? (
|
||||
<CollapsibleMessageContent item={item} context={context} />
|
||||
<CollapsibleTextItemContent item={item} context={context} />
|
||||
) : (
|
||||
<TextContent key={messageContentKey(item)} item={item} context={context} />
|
||||
<TextContent key={textItemContentKey(item)} item={item} context={context} />
|
||||
)}
|
||||
{item.kind === "message" && item.editedFiles && item.editedFiles.length > 0 ? <EditedFiles item={item} context={context} /> : null}
|
||||
{item.kind === "message" && item.referencedThread ? <ReferencedThread item={item} /> : null}
|
||||
|
|
@ -35,14 +35,14 @@ function MessageItem({ item, context }: { item: RenderableTextItem; context: Mes
|
|||
{item.kind === "system" && item.details && item.details.length > 0 ? (
|
||||
<SystemDetails details={item.details} />
|
||||
) : details && details.length > 0 ? (
|
||||
<MessageDetails itemId={item.id} details={details} context={context} />
|
||||
<TextItemDetails displayItemId={item.id} details={details} context={context} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CollapsibleMessageContent({ item, context }: { item: RenderableTextItem; context: MessageContentContext }): UiNode {
|
||||
const key = `message:${item.id}:expanded`;
|
||||
function CollapsibleTextItemContent({ item, context }: { item: TextDisplayItem; context: TextItemContentContext }): UiNode {
|
||||
const key = `text-item:${item.id}:expanded`;
|
||||
const renderModeKey = contentRenderMode(item);
|
||||
const collapseRef = useRef<HTMLDivElement | null>(null);
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -93,7 +93,7 @@ function CollapsibleMessageContent({ item, context }: { item: RenderableTextItem
|
|||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
<TextContent key={messageContentKey(item)} item={item} context={context} contentRef={contentRef} collapsed={overflows && !expanded} />
|
||||
<TextContent key={textItemContentKey(item)} item={item} context={context} contentRef={contentRef} collapsed={overflows && !expanded} />
|
||||
<details
|
||||
className="codex-panel__message-collapse-details"
|
||||
hidden={!overflows || expanded}
|
||||
|
|
@ -111,8 +111,8 @@ function CollapsibleMessageContent({ item, context }: { item: RenderableTextItem
|
|||
}
|
||||
|
||||
interface TextContentProps {
|
||||
item: RenderableTextItem;
|
||||
context: MessageContentContext;
|
||||
item: TextDisplayItem;
|
||||
context: TextItemContentContext;
|
||||
contentRef?: Ref<HTMLDivElement>;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
|
@ -158,11 +158,11 @@ function TextContent({ item, context, contentRef, collapsed = false }: TextConte
|
|||
);
|
||||
}
|
||||
|
||||
function messageContentKey(item: RenderableTextItem): string {
|
||||
function textItemContentKey(item: TextDisplayItem): string {
|
||||
return `${item.id}\u001f${contentRenderMode(item)}\u001f${item.text}`;
|
||||
}
|
||||
|
||||
function contentRenderMode(item: RenderableTextItem): "markdown" | "text" {
|
||||
function contentRenderMode(item: TextDisplayItem): "markdown" | "text" {
|
||||
if (item.kind !== "message") return "text";
|
||||
return item.messageKind === "proposedPlan" && item.messageState === "streaming" ? "text" : "markdown";
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ function userMessageCollapseHeight(element: HTMLElement): number {
|
|||
return Math.min(USER_MESSAGE_COLLAPSE_HEIGHT_PX, viewportHeight * 0.45);
|
||||
}
|
||||
|
||||
function messageClass(item: DisplayItem): string {
|
||||
function textItemClass(item: DisplayItem): string {
|
||||
const classes = ["codex-panel__message", `codex-panel__message--${item.role}`];
|
||||
if (item.kind === "approvalResult") classes.push("codex-panel__message--approval-result");
|
||||
if (item.kind === "userInputResult") classes.push("codex-panel__message--user-input-result");
|
||||
|
|
@ -15,7 +15,7 @@ import { createWorkMessageClassName } from "./work-message";
|
|||
|
||||
type PendingUserInputRequestId = PendingUserInput["requestId"];
|
||||
|
||||
export interface PendingRequestMessageActions {
|
||||
export interface PendingRequestBlockActions {
|
||||
resolveApproval: (approval: PendingApproval, action: ApprovalAction) => void;
|
||||
resolveUserInput: (input: PendingUserInput) => void;
|
||||
cancelUserInput: (input: PendingUserInput) => void;
|
||||
|
|
@ -23,24 +23,24 @@ export interface PendingRequestMessageActions {
|
|||
setUserInputDraft: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
export interface PendingRequestMessageDrafts {
|
||||
export interface PendingRequestBlockDrafts {
|
||||
values: ReadonlyMap<string, string>;
|
||||
draftKey: (requestId: PendingUserInputRequestId, questionId: string) => string;
|
||||
otherDraftKey: (requestId: PendingUserInputRequestId, questionId: string) => string;
|
||||
}
|
||||
|
||||
export function pendingRequestMessageNode(
|
||||
export function pendingRequestBlockNode(
|
||||
approvals: readonly PendingApproval[],
|
||||
pendingUserInputs: readonly PendingUserInput[],
|
||||
drafts: PendingRequestMessageDrafts,
|
||||
drafts: PendingRequestBlockDrafts,
|
||||
openDetails: ReadonlySet<string>,
|
||||
actions: PendingRequestMessageActions,
|
||||
actions: PendingRequestBlockActions,
|
||||
autoFocusRequested = false,
|
||||
consumeAutoFocus?: () => boolean,
|
||||
autoFocusSignature = "",
|
||||
): UiNode {
|
||||
return (
|
||||
<PendingRequestMessage
|
||||
<PendingRequestBlock
|
||||
approvals={approvals}
|
||||
pendingUserInputs={pendingUserInputs}
|
||||
drafts={drafts}
|
||||
|
|
@ -53,7 +53,7 @@ export function pendingRequestMessageNode(
|
|||
);
|
||||
}
|
||||
|
||||
function PendingRequestMessage({
|
||||
function PendingRequestBlock({
|
||||
approvals,
|
||||
pendingUserInputs,
|
||||
drafts,
|
||||
|
|
@ -65,9 +65,9 @@ function PendingRequestMessage({
|
|||
}: {
|
||||
approvals: readonly PendingApproval[];
|
||||
pendingUserInputs: readonly PendingUserInput[];
|
||||
drafts: PendingRequestMessageDrafts;
|
||||
drafts: PendingRequestBlockDrafts;
|
||||
openDetails: ReadonlySet<string>;
|
||||
actions: PendingRequestMessageActions;
|
||||
actions: PendingRequestBlockActions;
|
||||
autoFocusRequested: boolean;
|
||||
consumeAutoFocus: (() => boolean) | undefined;
|
||||
autoFocusSignature: string;
|
||||
|
|
@ -81,7 +81,7 @@ function PendingRequestMessage({
|
|||
}, [autoFocusRequested, consumeAutoFocus, autoFocusSignature]);
|
||||
if (approvals.length === 0 && pendingUserInputs.length === 0) return null;
|
||||
return (
|
||||
<div ref={requestRef} className={createWorkMessageClassName("codex-panel__pending-request-message", "warning")}>
|
||||
<div ref={requestRef} className={createWorkMessageClassName("codex-panel__pending-request-block", "warning")}>
|
||||
<div className="codex-panel__message-role">Request</div>
|
||||
{approvals.map((approval) => (
|
||||
<ApprovalCard key={String(approval.requestId)} approval={approval} openDetails={openDetails} actions={actions} />
|
||||
|
|
@ -116,7 +116,7 @@ function ApprovalCard({
|
|||
}: {
|
||||
approval: PendingApproval;
|
||||
openDetails: ReadonlySet<string>;
|
||||
actions: PendingRequestMessageActions;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
return (
|
||||
<PendingRequestCard className="codex-panel__approval">
|
||||
|
|
@ -148,7 +148,7 @@ function ApprovalDetails({
|
|||
}: {
|
||||
approval: PendingApproval;
|
||||
openDetails: ReadonlySet<string>;
|
||||
actions: PendingRequestMessageActions;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
const key = `approval:${String(approval.requestId)}:details`;
|
||||
return (
|
||||
|
|
@ -175,8 +175,8 @@ function UserInputCard({
|
|||
actions,
|
||||
}: {
|
||||
input: PendingUserInput;
|
||||
drafts: PendingRequestMessageDrafts;
|
||||
actions: PendingRequestMessageActions;
|
||||
drafts: PendingRequestBlockDrafts;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
return (
|
||||
<PendingRequestCard className="codex-panel__user-input">
|
||||
|
|
@ -217,8 +217,8 @@ function UserInputQuestions({
|
|||
actions,
|
||||
}: {
|
||||
input: PendingUserInput;
|
||||
drafts: PendingRequestMessageDrafts;
|
||||
actions: PendingRequestMessageActions;
|
||||
drafts: PendingRequestBlockDrafts;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -301,8 +301,8 @@ function OtherUserInputOption({
|
|||
groupName: string;
|
||||
current: string;
|
||||
optionLabels: ReadonlySet<string>;
|
||||
drafts: PendingRequestMessageDrafts;
|
||||
actions: PendingRequestMessageActions;
|
||||
drafts: PendingRequestBlockDrafts;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
const draftKey = drafts.draftKey(input.requestId, questionId);
|
||||
const otherKey = drafts.otherDraftKey(input.requestId, questionId);
|
||||
|
|
@ -382,8 +382,8 @@ function FreeformUserInput({
|
|||
questionText: string;
|
||||
isSecret: boolean;
|
||||
current: string;
|
||||
drafts: PendingRequestMessageDrafts;
|
||||
actions: PendingRequestMessageActions;
|
||||
drafts: PendingRequestBlockDrafts;
|
||||
actions: PendingRequestBlockActions;
|
||||
}): UiNode {
|
||||
const draftKey = drafts.draftKey(input.requestId, questionId);
|
||||
return (
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { pathRelativeToRoot } from "./paths";
|
||||
import { pathRelativeToRoot } from "../display/details/path-labels";
|
||||
import { definedProp } from "../../../utils";
|
||||
import type {
|
||||
ApprovalResultDisplayItem,
|
||||
|
|
@ -12,7 +12,7 @@ import type {
|
|||
HookDisplayItem,
|
||||
ReviewResultDisplayItem,
|
||||
ToolCallDisplayItem,
|
||||
} from "./types";
|
||||
} from "../display/types";
|
||||
|
||||
export type ToolResultDisplayItem =
|
||||
| CommandDisplayItem
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { toolResultView, type ToolResultDetailSection, type ToolResultDisplayItem, type ToolResultView } from "../display/tool-view";
|
||||
import { toolResultView, type ToolResultDetailSection, type ToolResultDisplayItem, type ToolResultView } from "./tool-result-view";
|
||||
import { renderRawDiffLines } from "../../../shared/diff/render";
|
||||
|
||||
export interface ToolResultRenderContext {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ButtonHTMLAttributes, ComponentChild as UiNode, TargetedKeyboardEvent } from "preact";
|
||||
import { useLayoutEffect, useRef } from "preact/hooks";
|
||||
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../display/runtime-status";
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../display/status/runtime";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
|
||||
type ButtonProps = ButtonHTMLAttributes & {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
import { useLayoutEffect, useState } from "preact/hooks";
|
||||
|
||||
import { activeAgentRunSummary, agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel } from "../display/agent";
|
||||
import { taskStatusMarker } from "../display/plan";
|
||||
import { activeAgentRunSummary, agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel } from "../display/stream/agent-summary";
|
||||
import type {
|
||||
AgentDisplayItem,
|
||||
AgentRunSummary,
|
||||
|
|
@ -77,6 +76,12 @@ function TaskProgressItem({ item }: { item: TaskProgressDisplayItem }): UiNode {
|
|||
);
|
||||
}
|
||||
|
||||
function taskStatusMarker(status: TaskProgressDisplayItem["steps"][number]["status"]): string {
|
||||
if (status === "completed") return "[x]";
|
||||
if (status === "inProgress") return "[>]";
|
||||
return "[ ]";
|
||||
}
|
||||
|
||||
function ContextCompactionItem({ item, context }: { item: ContextCompactionDisplayItem; context: WorkItemContext }): UiNode {
|
||||
const active = workItemsActiveTurnId(context) === item.turnId;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import { chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } f
|
|||
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
|
||||
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
|
||||
import type { CodexChatHost } from "./chat-host";
|
||||
import { createStructuredSystemItem, createSystemItem } from "./display/system";
|
||||
import { createStructuredSystemItem, createSystemItem } from "./display/items/system";
|
||||
import {
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
} from "./display/runtime-status";
|
||||
} from "./display/status/runtime";
|
||||
import { runtimeSnapshotForChatState } from "./runtime/snapshot";
|
||||
import { codexPanelDisplayTitle, getThreadTitle } from "../../domain/threads/model";
|
||||
import { connectionDiagnosticsModel } from "./panel/regions/toolbar";
|
||||
|
|
@ -574,19 +574,19 @@ export class CodexChatView extends ItemView {
|
|||
}
|
||||
|
||||
private async ensureRestoredThreadLoaded(): Promise<boolean> {
|
||||
return this.controllers.thread.restored.ensureLoaded();
|
||||
return this.controllers.thread.restoration.ensureLoaded();
|
||||
}
|
||||
|
||||
private isRestoredThreadPending(threadId: string): boolean {
|
||||
return this.controllers.thread.restored.isPending(threadId);
|
||||
return this.controllers.thread.restoration.isPending(threadId);
|
||||
}
|
||||
|
||||
private scheduleDeferredRestoredThreadHydration(): void {
|
||||
this.controllers.thread.restored.scheduleHydration();
|
||||
this.controllers.thread.restoration.scheduleHydration();
|
||||
}
|
||||
|
||||
private clearDeferredRestoredThreadHydration(): void {
|
||||
this.controllers.thread.restored.clearHydration();
|
||||
this.controllers.thread.restoration.clearHydration();
|
||||
}
|
||||
|
||||
private scheduleDeferredAppServerWarmup(): void {
|
||||
|
|
@ -601,11 +601,11 @@ export class CodexChatView extends ItemView {
|
|||
}
|
||||
|
||||
private restoredThreadPlaceholder() {
|
||||
return this.controllers.thread.restored.placeholder();
|
||||
return this.controllers.thread.restoration.placeholder();
|
||||
}
|
||||
|
||||
private restoredThreadTitle(): string | null {
|
||||
return this.controllers.thread.restored.title();
|
||||
return this.controllers.thread.restoration.title();
|
||||
}
|
||||
|
||||
private closeToolbarPanelOnOutsidePointer(event: PointerEvent): void {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.codex-panel__pending-request-message {
|
||||
.codex-panel__pending-request-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--codex-panel-item-gap);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/state/reducer";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/display/action-candidates";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/state/selectors";
|
||||
import {
|
||||
createPlanImplementationActions,
|
||||
type PlanImplementationActionsHost,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
upsertMcpServerDiagnostic,
|
||||
upsertMcpServerStatusDiagnostics,
|
||||
} from "../../../src/app-server/diagnostics";
|
||||
import { connectionDiagnosticSections, hasDiagnosticIssue } from "../../../src/features/chat/display/diagnostics";
|
||||
import { connectionDiagnosticSections, hasDiagnosticIssue } from "../../../src/features/chat/display/status/diagnostics";
|
||||
|
||||
describe("connection diagnostics", () => {
|
||||
it("formats base rows, capability probes, and MCP issues for /doctor", () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { activeAgentRunSummary, collabAgentStateExecutionState } from "../../../../src/features/chat/display/agent";
|
||||
import { displayBlocksForItems } from "../../../../src/features/chat/display/blocks";
|
||||
import { collabAgentStateExecutionState } from "../../../../src/features/chat/display/items/agent";
|
||||
import { activeAgentRunSummary } from "../../../../src/features/chat/display/stream/agent-summary";
|
||||
import { displayBlocksForItems } from "../../../../src/features/chat/display/stream/blocks";
|
||||
import {
|
||||
appendAssistantDelta,
|
||||
appendItemOutput,
|
||||
|
|
@ -9,22 +10,22 @@ import {
|
|||
appendPlanDelta,
|
||||
appendToolOutput,
|
||||
upsertDisplayItem,
|
||||
} from "../../../../src/features/chat/display/stream-updates";
|
||||
} from "../../../../src/features/chat/state/transcript-updates";
|
||||
import {
|
||||
normalizeProposedPlanMarkdown,
|
||||
planProgressDisplayItem,
|
||||
taskProgressDisplayItem,
|
||||
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";
|
||||
} from "../../../../src/features/chat/display/items/task-progress";
|
||||
import { normalizeProposedPlanMarkdown } from "../../../../src/features/chat/display/items/proposed-plan";
|
||||
import { pathRelativeToRoot } from "../../../../src/features/chat/display/details/path-labels";
|
||||
import { permissionRows } from "../../../../src/features/chat/display/details/permission-rows";
|
||||
import { autoReviewExecutionState, createAutoReviewResultItem, createReviewResultItem } from "../../../../src/features/chat/display/items/review-result";
|
||||
import {
|
||||
commandExecutionState,
|
||||
dynamicToolCallExecutionState,
|
||||
mcpToolCallExecutionState,
|
||||
patchApplyExecutionState,
|
||||
} from "../../../../src/features/chat/protocol/display-items";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns } from "../../../../src/features/chat/protocol/display-items";
|
||||
} from "../../../../src/features/chat/display/turn-items";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns } from "../../../../src/features/chat/display/turn-items";
|
||||
import { referencedThreadPrompt } from "../../../../src/domain/threads/reference";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
|
|
@ -244,7 +245,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
|
||||
it("formats structured plan progress as task progress", () => {
|
||||
expect(
|
||||
planProgressDisplayItem("t1", "Working plan", [
|
||||
taskProgressDisplayItem("t1", "Working plan", [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch UI", status: "inProgress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
|
|
@ -829,7 +830,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "contextCompaction",
|
||||
text: "Context compaction",
|
||||
turnId: "t1",
|
||||
itemId: "compact-1",
|
||||
sourceItemId: "compact-1",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -920,14 +921,14 @@ describe("streaming updates target item identity without mutating history", () =
|
|||
const items: DisplayItem[] = [
|
||||
{
|
||||
id: "a1",
|
||||
itemId: "a1",
|
||||
sourceItemId: "a1",
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
text: "hello",
|
||||
messageKind: "assistantResponse",
|
||||
messageState: "completed",
|
||||
},
|
||||
{ id: "tool1", itemId: "tool1", kind: "tool", role: "tool", text: "tool" },
|
||||
{ id: "tool1", sourceItemId: "tool1", kind: "tool", role: "tool", text: "tool" },
|
||||
];
|
||||
|
||||
const updated = appendAssistantDelta(items, "a1", "t1", " world");
|
||||
|
|
@ -939,10 +940,10 @@ describe("streaming updates target item identity without mutating history", () =
|
|||
});
|
||||
|
||||
it("appends tool text and output without mutating existing display items", () => {
|
||||
const tool: DisplayItem = { id: "tool1", itemId: "tool1", kind: "tool", role: "tool", text: "plan: " };
|
||||
const tool: DisplayItem = { id: "tool1", sourceItemId: "tool1", kind: "tool", role: "tool", text: "plan: " };
|
||||
const command: DisplayItem = {
|
||||
id: "cmd1",
|
||||
itemId: "cmd1",
|
||||
sourceItemId: "cmd1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "Command running",
|
||||
|
|
@ -1570,7 +1571,7 @@ describe("execution state uses typed status adapters before rendered text", () =
|
|||
it("does not overwrite streamed output with an empty completed item", () => {
|
||||
const streamed: DisplayItem = {
|
||||
id: "c1",
|
||||
itemId: "c1",
|
||||
sourceItemId: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "Command running",
|
||||
|
|
@ -1581,7 +1582,7 @@ describe("execution state uses typed status adapters before rendered text", () =
|
|||
};
|
||||
const completed: DisplayItem = {
|
||||
id: "c1",
|
||||
itemId: "c1",
|
||||
sourceItemId: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "npm test",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
isRollbackCandidateItem,
|
||||
rollbackCandidateFromItems,
|
||||
turnsAfterTurnId,
|
||||
} from "../../../../src/features/chat/display/action-candidates";
|
||||
} from "../../../../src/features/chat/display/item-actions";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
|
||||
describe("fork candidates", () => {
|
||||
|
|
@ -56,9 +56,9 @@ describe("fork candidates", () => {
|
|||
const candidates = forkCandidatesFromItems(items);
|
||||
|
||||
expect(candidates).toEqual([
|
||||
{ itemId: "a1", turnId: "turn-1" },
|
||||
{ itemId: "a2", turnId: "turn-2" },
|
||||
{ itemId: "a3", turnId: "turn-3" },
|
||||
{ displayItemId: "a1", turnId: "turn-1" },
|
||||
{ displayItemId: "a2", turnId: "turn-2" },
|
||||
{ displayItemId: "a3", turnId: "turn-3" },
|
||||
]);
|
||||
expect(isForkCandidateItem(expectPresent(items[4]), candidates)).toBe(true);
|
||||
expect(isForkCandidateItem(expectPresent(items[3]), candidates)).toBe(false);
|
||||
|
|
@ -96,7 +96,7 @@ describe("rollback candidate", () => {
|
|||
|
||||
const candidate = rollbackCandidateFromItems(items);
|
||||
|
||||
expect(candidate).toEqual({ turnId: "turn-2", itemId: "u2", text: "latest" });
|
||||
expect(candidate).toEqual({ turnId: "turn-2", displayItemId: "u2", text: "latest" });
|
||||
expect(isRollbackCandidateItem(expectPresent(items[2]), candidate)).toBe(true);
|
||||
expect(isRollbackCandidateItem(expectPresent(items[0]), candidate)).toBe(false);
|
||||
expect(isRollbackCandidateItem({ ...expectPresent(items[2]), turnId: "turn-other" }, candidate)).toBe(false);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ThreadGoal } from "../../../../src/app-server/thread-goal";
|
||||
import { goalChangeItem } from "../../../../src/features/chat/display/goal-messages";
|
||||
import type { ThreadGoal } from "../../../../../src/app-server/thread-goal";
|
||||
import { goalChangeItem } from "../../../../../src/features/chat/display/items/goal";
|
||||
|
||||
describe("goal display items", () => {
|
||||
it("keeps goal event summaries compact while retaining the full objective in details", () => {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { McpServerStatusSummary } from "../../../src/app-server/diagnostics";
|
||||
import { mcpStatusLines } from "../../../src/features/chat/display/diagnostics";
|
||||
import { mcpStatusLines } from "../../../src/features/chat/display/status/diagnostics";
|
||||
|
||||
function mcpServer(overrides: Partial<McpServerStatusSummary> = {}): McpServerStatusSummary {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/state/reducer";
|
||||
import { createToolbarArchiveConfirmState, ToolbarPanelController } from "../../../../../src/features/chat/panel/regions/toolbar";
|
||||
import type { ChatThreadActions } from "../../../../../src/features/chat/threads/thread-actions";
|
||||
import type { ChatThreadActions } from "../../../../../src/features/chat/threads/actions";
|
||||
|
||||
describe("ToolbarPanelController", () => {
|
||||
it("tracks archive confirmation and delegates archive actions", async () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ChatInboundController } from "../../../../../src/features/chat/protocol/inbound/controller";
|
||||
import { attachHookRunsToTurn } from "../../../../../src/features/chat/display/hooks";
|
||||
import { attachHookRunsToTurn } from "../../../../../src/features/chat/state/transcript-updates";
|
||||
import {
|
||||
activeTurnId,
|
||||
chatReducer,
|
||||
|
|
@ -1105,7 +1105,7 @@ describe("ChatInboundController", () => {
|
|||
{ id: "local-user-1", kind: "message", messageKind: "user", role: "user", text: "hello", turnId: "turn-active" },
|
||||
{
|
||||
id: "a1",
|
||||
itemId: "a1",
|
||||
sourceItemId: "a1",
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
messageKind: "assistantResponse",
|
||||
|
|
@ -1241,7 +1241,7 @@ describe("ChatInboundController", () => {
|
|||
{ id: "local-user-1", kind: "message", messageKind: "user", role: "user", text: "start", turnId: "turn-active" },
|
||||
{
|
||||
id: "a1",
|
||||
itemId: "a1",
|
||||
sourceItemId: "a1",
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
messageKind: "assistantResponse",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { AppServerClient } from "../../../../src/app-server/client";
|
|||
import type { AppServerThread } from "../../../../src/app-server/thread-model";
|
||||
import type { ArchiveExportAdapter } from "../../../../src/domain/threads/export";
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { createChatThreadActions, type ChatThreadActionsHost } from "../../../../src/features/chat/threads/thread-actions";
|
||||
import { createChatThreadActions, type ChatThreadActionsHost } from "../../../../src/features/chat/threads/actions";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { notices } from "../../../mocks/obsidian";
|
||||
|
|
@ -2,11 +2,11 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import type { AppServerClient } from "../../../../src/app-server/client";
|
||||
import type { ThreadGoal } from "../../../../src/app-server/thread-goal";
|
||||
import { createChatThreadGoalActions } from "../../../../src/features/chat/threads/thread-goal-actions";
|
||||
import { createGoalActions } from "../../../../src/features/chat/threads/goal-actions";
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("createChatThreadGoalActions", () => {
|
||||
describe("createGoalActions", () => {
|
||||
it("syncs the active thread goal into chat state", async () => {
|
||||
const state = createChatState();
|
||||
state.activeThread.id = "thread";
|
||||
|
|
@ -15,7 +15,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const client = { getThreadGoal: vi.fn().mockResolvedValue({ goal: currentGoal }) } as unknown as AppServerClient;
|
||||
const render = vi.fn();
|
||||
const refreshLiveState = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -38,7 +38,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const stateStore = createChatStateStore(state);
|
||||
const addSystemMessage = vi.fn();
|
||||
const client = { getThreadGoal: vi.fn().mockRejectedValue(new Error("offline")) } as unknown as AppServerClient;
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -70,7 +70,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
} as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const addGoalEvent = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -102,7 +102,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const update = deferred<never>();
|
||||
const client = { setThreadGoal: vi.fn().mockReturnValue(update.promise) } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -129,7 +129,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const clear = deferred<never>();
|
||||
const client = { clearThreadGoal: vi.fn().mockReturnValue(clear.promise) } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -157,7 +157,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const addGoalEvent = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -196,7 +196,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const injectThreadItems = vi.fn().mockRejectedValue(new Error("offline"));
|
||||
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -222,7 +222,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
});
|
||||
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -246,7 +246,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const injectThreadItems = vi.fn().mockResolvedValue({});
|
||||
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
|
||||
const addGoalEvent = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -271,7 +271,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const client = { setThreadGoal } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const addGoalEvent = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -294,7 +294,7 @@ describe("createChatThreadGoalActions", () => {
|
|||
const currentGoal = goal();
|
||||
const client = { getThreadGoal: vi.fn().mockResolvedValue({ goal: currentGoal }) } as unknown as AppServerClient;
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createChatThreadGoalActions({
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { ThreadHistoryController } from "../../../../src/features/chat/threads/thread-history-controller";
|
||||
import { HistoryController } from "../../../../src/features/chat/threads/history-controller";
|
||||
import type { AppServerClient } from "../../../../src/app-server/client";
|
||||
import type { AppServerThreadItem, AppServerTurn } from "../../../../src/app-server/turn-model";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("ThreadHistoryController", () => {
|
||||
describe("HistoryController", () => {
|
||||
it("keeps the latest history load when an older request resolves later", async () => {
|
||||
const first = deferred<ThreadTurnsListResponse>();
|
||||
const second = deferred<ThreadTurnsListResponse>();
|
||||
|
|
@ -96,7 +96,7 @@ function historyFixture(options: { threadTurnsList: ReturnType<typeof vi.fn> })
|
|||
const addSystemMessage = vi.fn();
|
||||
const keepCurrentScrollPosition = vi.fn();
|
||||
const showLatestPageAtBottom = vi.fn();
|
||||
const loader = new ThreadHistoryController({
|
||||
const loader = new HistoryController({
|
||||
stateStore,
|
||||
currentClient: () =>
|
||||
({
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { createThreadIdentitySync } from "../../../../src/features/chat/threads/thread-identity-sync";
|
||||
import type { RestoredThreadController } from "../../../../src/features/chat/threads/restored-thread-controller";
|
||||
import { createIdentitySync } from "../../../../src/features/chat/threads/identity-sync";
|
||||
import type { RestorationController } from "../../../../src/features/chat/threads/restoration-controller";
|
||||
import type { RestoredThreadPlaceholderState } from "../../../../src/features/chat/lifecycle";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
|
||||
|
|
@ -21,15 +21,15 @@ function createController() {
|
|||
const stateStore = createChatStateStore(createChatState());
|
||||
const restoredPlaceholder = vi.fn<() => RestoredThreadPlaceholderState | null>(() => null);
|
||||
const restoredRename = vi.fn();
|
||||
const restoredThread = {
|
||||
const restoration = {
|
||||
clear: vi.fn(),
|
||||
isPending: vi.fn(() => false),
|
||||
placeholder: restoredPlaceholder,
|
||||
rename: restoredRename,
|
||||
} as unknown as RestoredThreadController;
|
||||
} as unknown as RestorationController;
|
||||
const host = {
|
||||
stateStore,
|
||||
restoredThread,
|
||||
restoration,
|
||||
invalidateResumeWork: vi.fn(),
|
||||
clearDeferredRestoredThreadHydration: vi.fn(),
|
||||
resetThreadTurnPresence: vi.fn(),
|
||||
|
|
@ -38,10 +38,10 @@ function createController() {
|
|||
refreshLiveState: vi.fn(),
|
||||
render: vi.fn(),
|
||||
};
|
||||
return { controller: createThreadIdentitySync(host), host, restoredPlaceholder, restoredRename, stateStore };
|
||||
return { controller: createIdentitySync(host), host, restoredPlaceholder, restoredRename, stateStore };
|
||||
}
|
||||
|
||||
describe("createThreadIdentitySync", () => {
|
||||
describe("createIdentitySync", () => {
|
||||
it("clears the active thread when it is archived", () => {
|
||||
const { controller, host, stateStore } = createController();
|
||||
stateStore.dispatch({
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
type ThreadNamingClient,
|
||||
type ThreadNamingClientFactory,
|
||||
} from "../../../../src/app-server/thread-title-generation";
|
||||
import { firstNamingContextFromDisplayItems, namingContextFromDisplayItems } from "../../../../src/features/chat/threads/thread-naming";
|
||||
import { firstNamingContextFromDisplayItems, namingContextFromDisplayItems } from "../../../../src/features/chat/threads/naming";
|
||||
import type { AppServerClient, AppServerClientHandlers, AppServerStartStructuredTurnOptions } from "../../../../src/app-server/client";
|
||||
import type { AppServerInitialization } from "../../../../src/app-server/initialization";
|
||||
import type { RequestId, ServerNotification } from "../../../../src/app-server/types";
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { ThreadRenameController } from "../../../../src/features/chat/threads/thread-rename-controller";
|
||||
import { RenameController } from "../../../../src/features/chat/threads/rename-controller";
|
||||
import type { AppServerClient } from "../../../../src/app-server/client";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
import type { AppServerThreadItem, AppServerTurn } from "../../../../src/app-server/turn-model";
|
||||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("ThreadRenameController", () => {
|
||||
describe("RenameController", () => {
|
||||
it("notifies subscribers without rerendering after updating a controlled rename draft", () => {
|
||||
const { controller, render } = controllerFixture();
|
||||
const listener = vi.fn();
|
||||
|
|
@ -190,8 +190,8 @@ describe("ThreadRenameController", () => {
|
|||
});
|
||||
|
||||
function controllerFixture(
|
||||
overrides: Partial<ConstructorParameters<typeof ThreadRenameController>[0]> = {},
|
||||
): ConstructorParameters<typeof ThreadRenameController>[0] & { controller: ThreadRenameController; render: ReturnType<typeof vi.fn> } {
|
||||
overrides: Partial<ConstructorParameters<typeof RenameController>[0]> = {},
|
||||
): ConstructorParameters<typeof RenameController>[0] & { controller: RenameController; render: ReturnType<typeof vi.fn> } {
|
||||
const stateStore = createChatStateStore();
|
||||
stateStore.dispatch({ type: "thread-list/applied", threads: [threadFixture("thread")] });
|
||||
const render = vi.fn();
|
||||
|
|
@ -205,8 +205,8 @@ function controllerFixture(
|
|||
addSystemMessage: vi.fn(),
|
||||
notifyThreadRenamed: vi.fn(),
|
||||
...overrides,
|
||||
} satisfies ConstructorParameters<typeof ThreadRenameController>[0];
|
||||
return { ...host, controller: new ThreadRenameController(host), render };
|
||||
} satisfies ConstructorParameters<typeof RenameController>[0];
|
||||
return { ...host, controller: new RenameController(host), render };
|
||||
}
|
||||
|
||||
function fakeClient(options: { setThreadName?: ReturnType<typeof vi.fn> } = {}): AppServerClient {
|
||||
|
|
@ -3,16 +3,16 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import { RestoredThreadController } from "../../../../src/features/chat/threads/restored-thread-controller";
|
||||
import { RestorationController } from "../../../../src/features/chat/threads/restoration-controller";
|
||||
import { ChatViewDeferredTasks } from "../../../../src/features/chat/lifecycle";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("RestoredThreadController", () => {
|
||||
describe("RestorationController", () => {
|
||||
it("restores a placeholder and schedules deferred hydration", () => {
|
||||
vi.useFakeTimers();
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const resumeThread = vi.fn().mockResolvedValue(undefined);
|
||||
const controller = new RestoredThreadController({
|
||||
const controller = new RestorationController({
|
||||
deferredTasks: new ChatViewDeferredTasks(() => window),
|
||||
opened: () => true,
|
||||
resumeThread,
|
||||
|
|
@ -57,8 +57,8 @@ describe("RestoredThreadController", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function restoredThreadControllerFixture(overrides: Partial<ConstructorParameters<typeof RestoredThreadController>[0]> = {}) {
|
||||
return new RestoredThreadController({
|
||||
function restoredThreadControllerFixture(overrides: Partial<ConstructorParameters<typeof RestorationController>[0]> = {}) {
|
||||
return new RestorationController({
|
||||
deferredTasks: new ChatViewDeferredTasks(() => window),
|
||||
opened: () => false,
|
||||
resumeThread: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -2,9 +2,9 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import type { AppServerClient } from "../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import type { RestoredThreadController } from "../../../../src/features/chat/threads/restored-thread-controller";
|
||||
import { ThreadResumeController } from "../../../../src/features/chat/threads/thread-resume-controller";
|
||||
import type { ThreadHistoryController } from "../../../../src/features/chat/threads/thread-history-controller";
|
||||
import type { RestorationController } from "../../../../src/features/chat/threads/restoration-controller";
|
||||
import { ResumeController } from "../../../../src/features/chat/threads/resume-controller";
|
||||
import type { HistoryController } from "../../../../src/features/chat/threads/history-controller";
|
||||
import { ChatResumeWorkTracker } from "../../../../src/features/chat/lifecycle";
|
||||
import type { AppServerThread } from "../../../../src/app-server/thread-model";
|
||||
import type { Thread as PanelThread } from "../../../../src/domain/threads/model";
|
||||
|
|
@ -58,7 +58,7 @@ function activation(threadId: string): ThreadResumeResponse {
|
|||
|
||||
function createController(
|
||||
response: ThreadResumeResponse = activation("thread"),
|
||||
overrides: Partial<ConstructorParameters<typeof ThreadResumeController>[0]> = {},
|
||||
overrides: Partial<ConstructorParameters<typeof ResumeController>[0]> = {},
|
||||
) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const resumeThread = vi.fn().mockResolvedValue(response);
|
||||
|
|
@ -71,8 +71,8 @@ function createController(
|
|||
stateStore,
|
||||
vaultPath: "/vault",
|
||||
resumeWork: new ChatResumeWorkTracker(),
|
||||
history: { loadLatest, applyLatestPage, invalidate: invalidateHistory } as unknown as ThreadHistoryController,
|
||||
restoredThread: { clear: restoredClear } as unknown as RestoredThreadController,
|
||||
history: { loadLatest, applyLatestPage, invalidate: invalidateHistory } as unknown as HistoryController,
|
||||
restoration: { clear: restoredClear } as unknown as RestorationController,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
closing: () => false,
|
||||
|
|
@ -87,7 +87,7 @@ function createController(
|
|||
...overrides,
|
||||
};
|
||||
return {
|
||||
controller: new ThreadResumeController(host),
|
||||
controller: new ResumeController(host),
|
||||
host,
|
||||
applyLatestPage,
|
||||
invalidateHistory,
|
||||
|
|
@ -98,7 +98,7 @@ function createController(
|
|||
};
|
||||
}
|
||||
|
||||
describe("ThreadResumeController", () => {
|
||||
describe("ResumeController", () => {
|
||||
it("resumes the thread and loads its latest history", async () => {
|
||||
const { controller, host, loadLatest, restoredClear, resumeThread, stateStore } = createController();
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ThreadActivationSnapshot } from "../../../../src/app-server/thread-activation";
|
||||
import { resumedThreadAction } from "../../../../src/features/chat/threads/thread-resume";
|
||||
import { resumedThreadAction } from "../../../../src/features/chat/threads/resume";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
|
||||
describe("chat thread resume helpers", () => {
|
||||
|
|
@ -2,9 +2,9 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../src/features/chat/state/reducer";
|
||||
import {
|
||||
createThreadSelectionActions,
|
||||
type ThreadSelectionActionsHost,
|
||||
} from "../../../../src/features/chat/threads/thread-selection-actions";
|
||||
createSelectionActions,
|
||||
type SelectionActionsHost,
|
||||
} from "../../../../src/features/chat/threads/selection-actions";
|
||||
|
||||
function resumeThreadState(stateStore: ChatStateStore, threadId: string): void {
|
||||
stateStore.dispatch({
|
||||
|
|
@ -20,9 +20,9 @@ function resumeThreadState(stateStore: ChatStateStore, threadId: string): void {
|
|||
});
|
||||
}
|
||||
|
||||
function createController(overrides: Partial<ThreadSelectionActionsHost> = {}) {
|
||||
function createController(overrides: Partial<SelectionActionsHost> = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const host: ThreadSelectionActionsHost = {
|
||||
const host: SelectionActionsHost = {
|
||||
stateStore,
|
||||
closeForThreadSelection: vi.fn(),
|
||||
focusThreadInOpenView: vi.fn().mockResolvedValue(false),
|
||||
|
|
@ -30,10 +30,10 @@ function createController(overrides: Partial<ThreadSelectionActionsHost> = {}) {
|
|||
addSystemMessage: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
return { controller: createThreadSelectionActions(host), host, stateStore };
|
||||
return { controller: createSelectionActions(host), host, stateStore };
|
||||
}
|
||||
|
||||
describe("ThreadSelectionActions", () => {
|
||||
describe("SelectionActions", () => {
|
||||
it("focuses an already open thread without resuming it", async () => {
|
||||
const { controller, host } = createController({
|
||||
focusThreadInOpenView: vi.fn().mockResolvedValue(true),
|
||||
|
|
@ -5,7 +5,7 @@ import { act } from "preact/test-utils";
|
|||
import { MarkdownRenderer } from "obsidian";
|
||||
|
||||
import type { DisplayItem } from "../../../../../src/features/chat/display/types";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/display/action-candidates";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/state/selectors";
|
||||
import { MarkdownMessageRenderer } from "../../../../../src/features/chat/ui/message-stream/markdown-renderer";
|
||||
import { deferred } from "../../../../support/async";
|
||||
import { topLevelDetailsSummaries } from "../../../../support/dom";
|
||||
|
|
@ -133,7 +133,7 @@ describe("message stream rendering and message actions", () => {
|
|||
rows: [
|
||||
{ key: "status", value: "approved" },
|
||||
{ key: "action", value: "apply patch" },
|
||||
{ key: "files", value: "src/display/tool-view.ts\nsrc/ui/message-stream.ts" },
|
||||
{ key: "files", value: "src/ui/tool-result-view.ts\nsrc/ui/message-stream.ts" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -154,7 +154,7 @@ describe("message stream rendering and message actions", () => {
|
|||
expect(element.querySelector(".codex-panel__meta-grid")?.textContent).toContain("statusapproved");
|
||||
expect(element.querySelector(".codex-panel__meta-grid")?.textContent).toContain("actionapply patch");
|
||||
expect(element.querySelector(".codex-panel__meta-grid")?.textContent).toContain(
|
||||
"filessrc/display/tool-view.ts\nsrc/ui/message-stream.ts",
|
||||
"filessrc/ui/tool-result-view.ts\nsrc/ui/message-stream.ts",
|
||||
);
|
||||
expect([...element.querySelectorAll(".codex-panel__output-title")].map((title) => title.textContent)).toEqual([]);
|
||||
});
|
||||
|
|
@ -633,7 +633,7 @@ describe("message stream rendering and message actions", () => {
|
|||
it("hides copy action for the active assistant message while a turn is running", () => {
|
||||
const item = {
|
||||
id: "a-running",
|
||||
itemId: "a-running",
|
||||
sourceItemId: "a-running",
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
messageKind: "assistantResponse",
|
||||
|
|
@ -841,7 +841,7 @@ describe("message stream rendering and message actions", () => {
|
|||
details.dispatchEvent(new Event("toggle"));
|
||||
});
|
||||
}
|
||||
expect(openDetails.has("message:u1:expanded")).toBe(true);
|
||||
expect(openDetails.has("text-item:u1:expanded")).toBe(true);
|
||||
expect(content?.classList.contains("codex-panel__message-content--collapsed")).toBe(false);
|
||||
expect(details?.hidden).toBe(true);
|
||||
expect(onDetailsToggle).toHaveBeenCalled();
|
||||
|
|
@ -849,10 +849,10 @@ describe("message stream rendering and message actions", () => {
|
|||
void act(() => {
|
||||
document.body.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true }));
|
||||
});
|
||||
expect(openDetails.has("message:u1:expanded")).toBe(false);
|
||||
expect(openDetails.has("text-item:u1:expanded")).toBe(false);
|
||||
expect(content?.classList.contains("codex-panel__message-content--collapsed")).toBe(true);
|
||||
expect(details?.hidden).toBe(false);
|
||||
expect(onDetailsToggle).toHaveBeenCalledWith("message:u1:expanded", false);
|
||||
expect(onDetailsToggle).toHaveBeenCalledWith("text-item:u1:expanded", false);
|
||||
|
||||
element.querySelector<HTMLButtonElement>(".codex-panel__copy-message")?.click();
|
||||
expect(copyText).toHaveBeenCalledWith("full copied text");
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ describe("pending request renderer decisions", () => {
|
|||
pendingRequestActions({ resolveUserInput }),
|
||||
);
|
||||
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-message")).toHaveLength(1);
|
||||
expect(parent.querySelector(".codex-panel__pending-request-message")?.classList.contains("codex-panel__work-message")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__pending-request-message")?.classList.contains("codex-panel__work-message--warning")).toBe(
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-block")).toHaveLength(1);
|
||||
expect(parent.querySelector(".codex-panel__pending-request-block")?.classList.contains("codex-panel__work-message")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__pending-request-block")?.classList.contains("codex-panel__work-message--warning")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(parent.querySelector<HTMLButtonElement>(".codex-panel__pending-request-button.mod-cta")?.textContent).toBe("Submit");
|
||||
|
|
@ -202,7 +202,7 @@ describe("pending request renderer decisions", () => {
|
|||
pendingRequestActions(),
|
||||
);
|
||||
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-message")).toHaveLength(1);
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-block")).toHaveLength(1);
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-card")).toHaveLength(2);
|
||||
expect(parent.querySelectorAll(".codex-panel__pending-request-body")).toHaveLength(2);
|
||||
expect(parent.querySelector(".codex-panel__approval-body")).toBeNull();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { createMessageStreamContextPort } from "../../../../../src/features/chat
|
|||
describe("message stream context port", () => {
|
||||
it("closes other fork action details before opening a fork action detail", () => {
|
||||
const state = createChatState();
|
||||
state.ui.openDetails = new Set(["message:fork-actions:old", "message:u1:expanded"]);
|
||||
state.ui.openDetails = new Set(["message:fork-actions:old", "text-item:u1:expanded"]);
|
||||
const dispatched: ChatAction[] = [];
|
||||
|
||||
const port = createMessageStreamContextPort({
|
||||
|
|
@ -78,8 +78,8 @@ describe("message stream context port", () => {
|
|||
},
|
||||
});
|
||||
|
||||
port.setOpenDetail("message:u1:expanded", true);
|
||||
port.setOpenDetail("text-item:u1:expanded", true);
|
||||
|
||||
expect(dispatched).toEqual([{ type: "ui/detail-open-set", key: "message:u1:expanded", open: true }]);
|
||||
expect(dispatched).toEqual([{ type: "ui/detail-open-set", key: "text-item:u1:expanded", open: true }]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { act } from "preact/test-utils";
|
|||
|
||||
import type { PendingApproval } from "../../../../../src/features/chat/protocol/requests/approval";
|
||||
import type { PendingUserInput } from "../../../../../src/features/chat/protocol/requests/user-input";
|
||||
import { pendingRequestMessageNode, type PendingRequestMessageActions } from "../../../../../src/features/chat/ui/pending-request-message";
|
||||
import { pendingRequestBlockNode, type PendingRequestBlockActions } from "../../../../../src/features/chat/ui/pending-request-block";
|
||||
import type { ChatTurnLifecycleState } from "../../../../../src/features/chat/state/reducer";
|
||||
import { messageStreamBlocks as rawMessageStreamBlocks } from "../../../../../src/features/chat/ui/message-stream/stream-blocks";
|
||||
import type { MessageStreamBlock } from "../../../../../src/features/chat/ui/message-stream/context";
|
||||
|
|
@ -93,11 +93,11 @@ export function unmountUiRootInAct(parent: HTMLElement): void {
|
|||
});
|
||||
}
|
||||
|
||||
export function renderPendingRequestNode(parent: HTMLElement, ...args: Parameters<typeof pendingRequestMessageNode>): void {
|
||||
renderUiRootInAct(parent, pendingRequestMessageNode(...args));
|
||||
export function renderPendingRequestNode(parent: HTMLElement, ...args: Parameters<typeof pendingRequestBlockNode>): void {
|
||||
renderUiRootInAct(parent, pendingRequestBlockNode(...args));
|
||||
}
|
||||
|
||||
export function pendingRequestActions(overrides: Partial<PendingRequestMessageActions> = {}): PendingRequestMessageActions {
|
||||
export function pendingRequestActions(overrides: Partial<PendingRequestBlockActions> = {}): PendingRequestBlockActions {
|
||||
return {
|
||||
resolveApproval: vi.fn(),
|
||||
resolveUserInput: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ describe("ChatPanelShell", () => {
|
|||
store.dispatch({ type: "connection/status-set", status: "Working" });
|
||||
store.dispatch({ type: "ui/panel-set", panel: "status-panel" });
|
||||
store.dispatch({
|
||||
type: "transcript/system-message-added",
|
||||
type: "transcript/system-item-added",
|
||||
item: { id: "system-1", kind: "system", role: "system", text: "Model set." },
|
||||
});
|
||||
await settleShellEffects();
|
||||
|
|
@ -292,7 +292,7 @@ describe("ChatPanelShell", () => {
|
|||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "transcript/system-message-added",
|
||||
type: "transcript/system-item-added",
|
||||
item: { id: "system-1", kind: "system", role: "system", text: "Restored." },
|
||||
});
|
||||
await settleShellEffects();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { createChatStateStore } from "../../../../src/features/chat/state/reduce
|
|||
import { runtimeSnapshotForChatState } from "../../../../src/features/chat/runtime/snapshot";
|
||||
import { createToolbarArchiveConfirmState, ToolbarPanelController } from "../../../../src/features/chat/panel/regions/toolbar";
|
||||
import type { ChatPanelToolbarPorts } from "../../../../src/features/chat/panel/regions/ports";
|
||||
import type { ChatThreadActions } from "../../../../src/features/chat/threads/thread-actions";
|
||||
import type { ChatThreadActions } from "../../../../src/features/chat/threads/actions";
|
||||
import { renderChatPanelShell, unmountChatPanelShell } from "../../../../src/features/chat/ui/shell";
|
||||
import { chatPanelToolbarRegionNode } from "../../../../src/features/chat/panel/regions/render";
|
||||
import { installObsidianDomShims } from "../../../support/dom";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "../../../src/app-server/runtime-config";
|
||||
import { createChatState } from "../../../src/features/chat/state/reducer";
|
||||
import { composerMetaViewModel, composerPlaceholder } from "../../../src/features/chat/panel/regions/composer";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../src/features/chat/display/runtime-status";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../src/features/chat/display/status/runtime";
|
||||
import { runtimeComposerChoices } from "../../../src/features/chat/panel/regions/composer";
|
||||
import { runtimeSnapshotForChatState } from "../../../src/features/chat/runtime/snapshot";
|
||||
import { toolbarViewModel } from "../../../src/features/chat/panel/regions/toolbar";
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import {
|
|||
requestedTurnCollaborationModeSettings,
|
||||
serviceTierRequestForThreadStart,
|
||||
} from "../../src/features/chat/runtime/thread-settings";
|
||||
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/display/runtime-status";
|
||||
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/display/status/runtime";
|
||||
|
||||
describe("runtime settings", () => {
|
||||
it("parses model overrides", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue