mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Constrain generated Thread model to app-server boundary
This commit is contained in:
parent
6598e62a14
commit
3029bc478d
54 changed files with 258 additions and 175 deletions
|
|
@ -3,7 +3,7 @@ import type { ConfigReadResponse } from "../generated/app-server/v2/ConfigReadRe
|
|||
import type { Model } from "../generated/app-server/v2/Model";
|
||||
import type { RateLimitSnapshot } from "../generated/app-server/v2/RateLimitSnapshot";
|
||||
import type { SkillMetadata } from "../generated/app-server/v2/SkillMetadata";
|
||||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
|
||||
export interface SharedAppServerMetadata {
|
||||
effectiveConfig: ConfigReadResponse | null;
|
||||
|
|
@ -16,7 +16,7 @@ export interface SharedAppServerMetadata {
|
|||
type SharedCache<T> = { kind: "unloaded" } | { kind: "loaded"; data: T };
|
||||
|
||||
export interface SharedAppServerState {
|
||||
threads: SharedCache<readonly Thread[]>;
|
||||
threads: SharedCache<readonly PanelThread[]>;
|
||||
appServerMetadata: SharedCache<SharedAppServerMetadata>;
|
||||
availableModels: readonly Model[];
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ export function createSharedAppServerState(): SharedAppServerState {
|
|||
};
|
||||
}
|
||||
|
||||
export function applySharedThreadList(state: SharedAppServerState, threads: readonly Thread[]): SharedAppServerState {
|
||||
export function applySharedThreadList(state: SharedAppServerState, threads: readonly PanelThread[]): SharedAppServerState {
|
||||
return {
|
||||
...state,
|
||||
threads: { kind: "loaded", data: cloneThreads(threads) },
|
||||
|
|
@ -57,7 +57,7 @@ export function applySharedModels(state: SharedAppServerState, models: readonly
|
|||
};
|
||||
}
|
||||
|
||||
export function cachedSharedThreadList(state: SharedAppServerState): readonly Thread[] | null {
|
||||
export function cachedSharedThreadList(state: SharedAppServerState): readonly PanelThread[] | null {
|
||||
return state.threads.kind === "loaded" ? cloneThreads(state.threads.data) : null;
|
||||
}
|
||||
|
||||
|
|
@ -82,8 +82,8 @@ function cloneSharedAppServerMetadata(metadata: SharedAppServerMetadata): Shared
|
|||
};
|
||||
}
|
||||
|
||||
function cloneThreads(threads: readonly Thread[]): Thread[] {
|
||||
return threads.map((thread) => ({ ...thread, turns: [...thread.turns] }));
|
||||
function cloneThreads(threads: readonly PanelThread[]): PanelThread[] {
|
||||
return threads.map((thread) => ({ ...thread }));
|
||||
}
|
||||
|
||||
function cloneModels(models: readonly Model[]): Model[] {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { Model } from "../generated/app-server/v2/Model";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
import {
|
||||
applySharedAppServerMetadata,
|
||||
applySharedModels,
|
||||
|
|
@ -12,16 +12,16 @@ import {
|
|||
type SharedAppServerState,
|
||||
} from "./shared-cache-state";
|
||||
|
||||
type ThreadListRefreshLifecycleState = { kind: "idle" } | { kind: "refreshing"; promise: Promise<readonly Thread[]> };
|
||||
type ThreadListRefreshLifecycleState = { kind: "idle" } | { kind: "refreshing"; promise: Promise<readonly PanelThread[]> };
|
||||
|
||||
export class SharedAppServerCache {
|
||||
private state: SharedAppServerState = createSharedAppServerState();
|
||||
private threadListRefreshLifecycle: ThreadListRefreshLifecycleState = { kind: "idle" };
|
||||
|
||||
refreshThreadList(
|
||||
fetchThreads: () => Promise<readonly Thread[]>,
|
||||
onSnapshot?: (threads: readonly Thread[]) => void,
|
||||
): Promise<readonly Thread[]> {
|
||||
fetchThreads: () => Promise<readonly PanelThread[]>,
|
||||
onSnapshot?: (threads: readonly PanelThread[]) => void,
|
||||
): Promise<readonly PanelThread[]> {
|
||||
if (this.threadListRefreshLifecycle.kind === "refreshing") return this.threadListRefreshLifecycle.promise;
|
||||
const promise = fetchThreads()
|
||||
.then((threads) => {
|
||||
|
|
@ -38,11 +38,11 @@ export class SharedAppServerCache {
|
|||
return promise;
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void {
|
||||
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
|
||||
this.state = applySharedThreadList(this.state, threads);
|
||||
}
|
||||
|
||||
cachedThreadList(): readonly Thread[] | null {
|
||||
cachedThreadList(): readonly PanelThread[] | null {
|
||||
return cachedSharedThreadList(this.state);
|
||||
}
|
||||
|
||||
|
|
|
|||
29
src/app-server/thread-model.ts
Normal file
29
src/app-server/thread-model.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
|
||||
export function panelThreadFromAppServerThread(thread: Thread, options: { archived?: boolean } = {}): PanelThread {
|
||||
return {
|
||||
id: thread.id,
|
||||
preview: normalizeString(thread.preview),
|
||||
name: normalizeNullableString(thread.name),
|
||||
archived: options.archived ?? false,
|
||||
createdAt: finiteTimestamp(thread.createdAt),
|
||||
updatedAt: finiteTimestamp(thread.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
export function panelThreadsFromAppServerThreads(threads: readonly Thread[], options: { archived?: boolean } = {}): PanelThread[] {
|
||||
return threads.map((thread) => panelThreadFromAppServerThread(thread, options));
|
||||
}
|
||||
|
||||
function normalizeNullableString(value: string | null): string | null {
|
||||
return value === null ? null : normalizeString(value);
|
||||
}
|
||||
|
||||
function normalizeString(value: string): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function finiteTimestamp(value: number): number {
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { Turn } from "../../generated/app-server/v2/Turn";
|
||||
import { shortThreadId } from "../../utils";
|
||||
import { getThreadTitle } from "./model";
|
||||
import { getThreadTitle, type PanelThread } from "./model";
|
||||
import { referencedThreadDisplayFromPrompt } from "./reference";
|
||||
import { turnTranscriptEntries, type TurnTranscriptEntry } from "./transcript";
|
||||
|
||||
|
|
@ -37,8 +36,12 @@ export interface ArchiveExportSettings {
|
|||
vaultPath?: string;
|
||||
}
|
||||
|
||||
export interface ArchiveThreadInput extends PanelThread {
|
||||
turns: Turn[];
|
||||
}
|
||||
|
||||
export async function exportArchivedThreadMarkdown(
|
||||
thread: Thread,
|
||||
thread: ArchiveThreadInput,
|
||||
settings: ArchiveExportSettings,
|
||||
adapter: ArchiveExportAdapter,
|
||||
now = new Date(),
|
||||
|
|
@ -53,7 +56,7 @@ export async function exportArchivedThreadMarkdown(
|
|||
return { path };
|
||||
}
|
||||
|
||||
export function markdownFromThread(thread: Thread, exportedAt = new Date(), settings?: Partial<ArchiveExportSettings>): string {
|
||||
export function markdownFromThread(thread: ArchiveThreadInput, exportedAt = new Date(), settings?: Partial<ArchiveExportSettings>): string {
|
||||
const title = exportThreadTitle(thread);
|
||||
const tags = normalizedArchiveTags(settings?.archiveExportTags ?? "");
|
||||
const lines = [
|
||||
|
|
@ -172,7 +175,7 @@ function stripMatchingQuotes(value: string): string {
|
|||
return (first === `"` || first === `'`) && first === last ? value.slice(1, -1) : value;
|
||||
}
|
||||
|
||||
function templateContext(thread: Thread, now: Date): TemplateContext {
|
||||
function templateContext(thread: ArchiveThreadInput, now: Date): TemplateContext {
|
||||
const title = sanitizePathSegment(exportThreadTitle(thread));
|
||||
return {
|
||||
date: formatDate(now),
|
||||
|
|
@ -239,7 +242,7 @@ async function uniqueMarkdownPath(adapter: ArchiveExportAdapter, folder: string,
|
|||
return candidate;
|
||||
}
|
||||
|
||||
function exportThreadTitle(thread: Thread): string {
|
||||
function exportThreadTitle(thread: ArchiveThreadInput): string {
|
||||
return getThreadTitle(thread) || "Untitled thread";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,34 @@
|
|||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import { shortThreadId } from "../../utils";
|
||||
|
||||
const MAX_THREAD_DISPLAY_TITLE_LENGTH = 96;
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function getThreadTitle(thread: Thread): string {
|
||||
export interface PanelThread {
|
||||
id: string;
|
||||
preview: string;
|
||||
name: string | null;
|
||||
archived: boolean;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export function getThreadTitle(thread: PanelThread): string {
|
||||
return (
|
||||
[thread.name, thread.preview, thread.id].map((value) => (typeof value === "string" ? normalizeTitle(value) : "")).find(Boolean) ??
|
||||
thread.id
|
||||
);
|
||||
}
|
||||
|
||||
export function explicitThreadName(thread: Thread): string | null {
|
||||
export function explicitThreadName(thread: PanelThread): string | null {
|
||||
const name = typeof thread.name === "string" ? normalizeTitle(thread.name) : "";
|
||||
return name.length > 0 ? name : null;
|
||||
}
|
||||
|
||||
export function codexPanelDisplayTitle(activeThreadId: string | null, threads: readonly Thread[], fallbackTitle?: string | null): string {
|
||||
export function codexPanelDisplayTitle(
|
||||
activeThreadId: string | null,
|
||||
threads: readonly PanelThread[],
|
||||
fallbackTitle?: string | null,
|
||||
): string {
|
||||
if (!activeThreadId) return "Codex";
|
||||
|
||||
const thread = threads.find((item) => item.id === activeThreadId);
|
||||
|
|
@ -24,24 +36,24 @@ export function codexPanelDisplayTitle(activeThreadId: string | null, threads: r
|
|||
return title ? `Codex: ${title}` : "Codex";
|
||||
}
|
||||
|
||||
export function inheritedForkThreadName(threadId: string, threads: readonly Thread[]): string | null {
|
||||
export function inheritedForkThreadName(threadId: string, threads: readonly PanelThread[]): string | null {
|
||||
const thread = threads.find((item) => item.id === threadId);
|
||||
return thread ? explicitThreadName(thread) : null;
|
||||
}
|
||||
|
||||
export function upsertThread(threads: readonly Thread[], thread: Thread): Thread[] {
|
||||
export function upsertThread(threads: readonly PanelThread[], thread: PanelThread): PanelThread[] {
|
||||
const index = threads.findIndex((item) => item.id === thread.id);
|
||||
if (index === -1) return [thread, ...threads];
|
||||
return threads.map((item, itemIndex) => (itemIndex === index ? { ...item, ...thread } : item));
|
||||
}
|
||||
|
||||
export function archivedThreadDisplayTitle(thread: Thread): string {
|
||||
export function archivedThreadDisplayTitle(thread: PanelThread): string {
|
||||
const title = fullThreadTitle(thread);
|
||||
if (!title || title === thread.id || UUID_PATTERN.test(title)) return "Untitled archived thread";
|
||||
return truncateTitle(title, MAX_THREAD_DISPLAY_TITLE_LENGTH);
|
||||
}
|
||||
|
||||
function fullThreadTitle(thread: Thread): string {
|
||||
function fullThreadTitle(thread: PanelThread): string {
|
||||
return normalizeTitle(getThreadTitle(thread));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { Turn } from "../../generated/app-server/v2/Turn";
|
||||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
import { shortThreadId } from "../../utils";
|
||||
import { getThreadTitle } from "./model";
|
||||
import { getThreadTitle, type PanelThread } from "./model";
|
||||
import { chronologicalTurnConversationSummaries } from "./transcript";
|
||||
|
||||
export const REFERENCED_THREAD_TURN_LIMIT = 20;
|
||||
|
|
@ -35,7 +34,7 @@ export function referencedThreadTurns(turns: Turn[]): ReferencedThreadTurn[] {
|
|||
return chronologicalTurnConversationSummaries(turns);
|
||||
}
|
||||
|
||||
export function referencedThreadPrompt(thread: Thread, turns: ReferencedThreadTurn[], userRequest: string): string {
|
||||
export function referencedThreadPrompt(thread: PanelThread, turns: ReferencedThreadTurn[], userRequest: string): string {
|
||||
const reference = referencedThreadDisplay(thread, turns.length);
|
||||
const envelope = referencedThreadEnvelope(reference, userRequest);
|
||||
|
||||
|
|
@ -58,11 +57,11 @@ export function referencedThreadPrompt(thread: Thread, turns: ReferencedThreadTu
|
|||
].join("\n");
|
||||
}
|
||||
|
||||
function referencedThreadStatus(thread: Thread, count: number): string {
|
||||
function referencedThreadStatus(thread: PanelThread, count: number): string {
|
||||
return `Referencing ${shortThreadId(thread.id)} (${String(count)}/${String(REFERENCED_THREAD_TURN_LIMIT)} turns).`;
|
||||
}
|
||||
|
||||
function referencedThreadDisplay(thread: Thread, count: number): ReferencedThreadDisplay {
|
||||
function referencedThreadDisplay(thread: PanelThread, count: number): ReferencedThreadDisplay {
|
||||
return {
|
||||
threadId: thread.id,
|
||||
title: getThreadTitle(thread),
|
||||
|
|
@ -72,7 +71,7 @@ function referencedThreadDisplay(thread: Thread, count: number): ReferencedThrea
|
|||
}
|
||||
|
||||
export function referencedThreadInput(
|
||||
thread: Thread,
|
||||
thread: PanelThread,
|
||||
turns: readonly ReferencedThreadTurn[],
|
||||
userRequest: string,
|
||||
messageInput: UserInput[],
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import { panelThreadFromAppServerThread, panelThreadsFromAppServerThreads } from "../../../app-server/thread-model";
|
||||
import { upsertThread } from "../../../domain/threads/model";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../../../runtime/effective-settings";
|
||||
import { resumedThreadAction } from "../threads/thread-resume";
|
||||
import type { ChatAppServerBaseHost } from "./shared";
|
||||
|
||||
interface StartedThreadSummary {
|
||||
threadId: string;
|
||||
}
|
||||
|
||||
export interface ChatAppServerThreadActionsHost extends ChatAppServerBaseHost {
|
||||
runtimeSnapshot: () => RuntimeSnapshot;
|
||||
publishThreadList: (threads: readonly Thread[]) => void;
|
||||
publishThreadList: (threads: readonly PanelThread[]) => void;
|
||||
syncThreadGoal: (threadId: string) => void;
|
||||
}
|
||||
|
||||
export interface ChatAppServerThreadActions {
|
||||
applyThreadList: (threads: readonly Thread[]) => void;
|
||||
loadThreadList: () => Promise<readonly Thread[]>;
|
||||
startThread: (preview?: string, options?: { syncGoal?: boolean }) => Promise<Awaited<ReturnType<AppServerClient["startThread"]>> | null>;
|
||||
applyThreadList: (threads: readonly PanelThread[]) => void;
|
||||
loadThreadList: () => Promise<readonly PanelThread[]>;
|
||||
startThread: (preview?: string, options?: { syncGoal?: boolean }) => Promise<StartedThreadSummary | null>;
|
||||
}
|
||||
|
||||
export function createChatAppServerThreadActions(host: ChatAppServerThreadActionsHost): ChatAppServerThreadActions {
|
||||
|
|
@ -27,34 +31,34 @@ export function createChatAppServerThreadActions(host: ChatAppServerThreadAction
|
|||
};
|
||||
}
|
||||
|
||||
function applyThreadList(host: ChatAppServerThreadActionsHost, threads: readonly Thread[]): void {
|
||||
function applyThreadList(host: ChatAppServerThreadActionsHost, threads: readonly PanelThread[]): void {
|
||||
host.stateStore.dispatch({ type: "thread-list/applied", threads, threadsLoaded: true });
|
||||
}
|
||||
|
||||
async function loadThreadList(host: ChatAppServerThreadActionsHost): Promise<readonly Thread[]> {
|
||||
async function loadThreadList(host: ChatAppServerThreadActionsHost): Promise<readonly PanelThread[]> {
|
||||
const client = host.currentClient();
|
||||
if (!client) throw new Error("Codex app-server is not connected.");
|
||||
const response = await client.listThreads(host.vaultPath);
|
||||
return response.data;
|
||||
return panelThreadsFromAppServerThreads(response.data);
|
||||
}
|
||||
|
||||
async function startThread(
|
||||
host: ChatAppServerThreadActionsHost,
|
||||
preview?: string,
|
||||
options: { syncGoal?: boolean } = {},
|
||||
): Promise<Awaited<ReturnType<AppServerClient["startThread"]>> | null> {
|
||||
): Promise<StartedThreadSummary | null> {
|
||||
const client = host.currentClient();
|
||||
if (!client) return null;
|
||||
const serviceTier = requestedOrConfiguredServiceTier(host.runtimeSnapshot());
|
||||
const response = await client.startThread(host.vaultPath, serviceTier);
|
||||
const state = host.stateStore.getState();
|
||||
const fallbackPreview = preview?.trim();
|
||||
const thread =
|
||||
const appServerThread =
|
||||
response.thread.preview.trim().length > 0 || !fallbackPreview ? response.thread : { ...response.thread, preview: fallbackPreview };
|
||||
const thread = panelThreadFromAppServerThread(appServerThread);
|
||||
const listedThreads = upsertThread(state.threadList.listedThreads, thread);
|
||||
const resumedResponse = thread === response.thread ? response : { ...response, thread };
|
||||
host.stateStore.dispatch(resumedThreadAction({ response: resumedResponse, listedThreads }));
|
||||
host.stateStore.dispatch(resumedThreadAction({ response: { ...response, thread }, listedThreads }));
|
||||
host.publishThreadList(listedThreads);
|
||||
if (options.syncGoal ?? true) host.syncThreadGoal(response.thread.id);
|
||||
return response;
|
||||
return { threadId: response.thread.id };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
|
||||
import type { CodexPanelSettings } from "../../settings/model";
|
||||
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
||||
|
|
@ -13,9 +13,9 @@ export interface CodexChatHost {
|
|||
notifyThreadRenamed(threadId: string, name: string | null): void;
|
||||
refreshThreadsViewLiveState(): void;
|
||||
refreshSharedThreadListFromOpenSurface(): void;
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
|
||||
cachedThreadList(): readonly Thread[] | null;
|
||||
applyThreadListSnapshot(threads: readonly PanelThread[]): void;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
|
||||
cachedThreadList(): readonly PanelThread[] | null;
|
||||
publishAppServerMetadata(metadata: SharedAppServerMetadata): void;
|
||||
cachedAppServerMetadata(): SharedAppServerMetadata | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { InitializeResponse } from "../../generated/app-server/InitializeResponse";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { ThreadTokenUsage } from "../../generated/app-server/v2/ThreadTokenUsage";
|
||||
import type { ChatAction, PendingTurnStart } from "./chat-state";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
|
|
@ -24,7 +24,7 @@ export function clearActiveThreadAction(): ChatAction {
|
|||
return { type: "active-thread/cleared" };
|
||||
}
|
||||
|
||||
export function applyThreadListAction(threads: readonly Thread[], threadsLoaded?: boolean): ChatAction {
|
||||
export function applyThreadListAction(threads: readonly PanelThread[], threadsLoaded?: boolean): ChatAction {
|
||||
return { type: "thread-list/applied", threads, ...(threadsLoaded === undefined ? {} : { threadsLoaded }) };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { activeTurnId as selectActiveTurnId, chatTurnBusy, pendingTurnStart } from "./chat-state";
|
||||
import type { ChatState, PendingTurnStart } from "./chat-state";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { PendingApproval } from "./requests/approval";
|
||||
import type { PendingUserInput } from "./requests/user-input";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
|
|
@ -17,7 +17,7 @@ export interface SubmissionStateSnapshot {
|
|||
activeThreadId: string | null;
|
||||
activeTurnId: string | null;
|
||||
busy: boolean;
|
||||
listedThreads: readonly Thread[];
|
||||
listedThreads: readonly PanelThread[];
|
||||
displayItems: readonly DisplayItem[];
|
||||
pendingTurnStart: PendingTurnStart | null;
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ export function canSwitchToThread(state: ChatState, threadId: string): boolean {
|
|||
return !chatTurnBusy(state) || threadId === state.activeThread.id;
|
||||
}
|
||||
|
||||
export function listedThreads(state: ChatState): readonly Thread[] {
|
||||
export function listedThreads(state: ChatState): readonly PanelThread[] {
|
||||
return state.threadList.listedThreads;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { ConfigReadResponse } from "../../generated/app-server/v2/ConfigRea
|
|||
import type { Model } from "../../generated/app-server/v2/Model";
|
||||
import type { RateLimitSnapshot } from "../../generated/app-server/v2/RateLimitSnapshot";
|
||||
import type { SkillMetadata } from "../../generated/app-server/v2/SkillMetadata";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { ThreadGoal } from "../../generated/app-server/v2/ThreadGoal";
|
||||
import type { ThreadSettingsUpdateParams } from "../../generated/app-server/v2/ThreadSettingsUpdateParams";
|
||||
import type { ThreadTokenUsage } from "../../generated/app-server/v2/ThreadTokenUsage";
|
||||
|
|
@ -57,14 +57,13 @@ interface ChatConnectionState {
|
|||
}
|
||||
|
||||
interface ChatThreadListState {
|
||||
listedThreads: readonly Thread[];
|
||||
listedThreads: readonly PanelThread[];
|
||||
threadsLoaded: boolean;
|
||||
}
|
||||
|
||||
export interface ChatActiveThreadState {
|
||||
id: string | null;
|
||||
cwd: string | null;
|
||||
creationCliVersion: string | null;
|
||||
goal: ThreadGoal | null;
|
||||
tokenUsage: ThreadTokenUsage | null;
|
||||
}
|
||||
|
|
@ -145,7 +144,7 @@ type ConnectionAction =
|
|||
|
||||
interface ThreadListAppliedAction {
|
||||
type: "thread-list/applied";
|
||||
threads?: readonly Thread[];
|
||||
threads?: readonly PanelThread[];
|
||||
threadsLoaded?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +152,7 @@ type ThreadListAction = ThreadListAppliedAction;
|
|||
|
||||
export interface ActiveThreadResumedAction {
|
||||
type: "active-thread/resumed";
|
||||
thread: Thread;
|
||||
thread: PanelThread;
|
||||
cwd: string;
|
||||
model: string | null;
|
||||
reasoningEffort: ReasoningEffort | null;
|
||||
|
|
@ -163,7 +162,7 @@ export interface ActiveThreadResumedAction {
|
|||
activePermissionProfile: ActivePermissionProfile | null;
|
||||
displayItems?: readonly DisplayItem[];
|
||||
status?: string;
|
||||
listedThreads?: readonly Thread[];
|
||||
listedThreads?: readonly PanelThread[];
|
||||
}
|
||||
|
||||
export interface ActiveThreadSettingsAppliedAction {
|
||||
|
|
@ -431,7 +430,6 @@ function reduceActiveThreadResumedTransition(state: ChatState, action: ActiveThr
|
|||
activeThread: {
|
||||
id: action.thread.id,
|
||||
cwd: action.cwd,
|
||||
creationCliVersion: action.thread.cliVersion,
|
||||
goal: null,
|
||||
tokenUsage: null,
|
||||
},
|
||||
|
|
@ -481,7 +479,6 @@ function reduceActiveThreadRestoredPlaceholderTransition(state: ChatState, actio
|
|||
activeThread: {
|
||||
id: action.threadId,
|
||||
cwd: null,
|
||||
creationCliVersion: null,
|
||||
goal: null,
|
||||
tokenUsage: null,
|
||||
},
|
||||
|
|
@ -826,7 +823,6 @@ function initialActiveThreadState(): ChatActiveThreadState {
|
|||
return {
|
||||
id: null,
|
||||
cwd: null,
|
||||
creationCliVersion: null,
|
||||
goal: null,
|
||||
tokenUsage: null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Model } from "../../../generated/app-server/v2/Model";
|
||||
import type { SkillMetadata } from "../../../generated/app-server/v2/SkillMetadata";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import { prepareFuzzySearch, sortSearchResults, type SearchResult } from "obsidian";
|
||||
import {
|
||||
findModelByIdOrName,
|
||||
|
|
@ -59,7 +59,7 @@ export function activeComposerSuggestions(
|
|||
beforeCursor: string,
|
||||
notes: NoteCandidate[],
|
||||
skills: readonly SkillMetadata[],
|
||||
threads: readonly Thread[] = [],
|
||||
threads: readonly PanelThread[] = [],
|
||||
models: readonly Model[] = [],
|
||||
currentModel: string | null = null,
|
||||
): ComposerSuggestion[] {
|
||||
|
|
@ -275,7 +275,7 @@ function activeSlashSubcommandSuggestions(beforeCursor: string): ComposerSuggest
|
|||
}));
|
||||
}
|
||||
|
||||
function activeThreadCommandSuggestions(beforeCursor: string, threads: readonly Thread[]): ComposerSuggestion[] | null {
|
||||
function activeThreadCommandSuggestions(beforeCursor: string, threads: readonly PanelThread[]): ComposerSuggestion[] | null {
|
||||
const completion = activeCommandArgumentCompletionQuery(beforeCursor, /^\/(?:resume|refer|archive|rename)\s+([^\s\n]{0,120})$/);
|
||||
if (!completion) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export interface ConnectionDiagnosticsInput {
|
|||
connected: boolean;
|
||||
configuredCommand: string;
|
||||
initializeResponse: InitializeResponse | null;
|
||||
activeThreadCreationCliVersion: string | null;
|
||||
diagnostics: AppServerDiagnostics;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +33,6 @@ export function connectionDiagnosticSections(input: ConnectionDiagnosticsInput):
|
|||
{ label: "panel client", value: CLIENT_VERSION },
|
||||
{ label: "platform", value: appServerPlatform(input.initializeResponse) },
|
||||
{ label: "Codex home", value: input.initializeResponse?.codexHome ?? "(not connected)" },
|
||||
{ label: "thread created by CLI", value: input.activeThreadCreationCliVersion ?? "(none)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Thread } from "../../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../../domain/threads/model";
|
||||
import { getThreadTitle } from "../../../../domain/threads/model";
|
||||
import { effectiveConfigSections, rateLimitSummary } from "../../../../runtime/status-summary";
|
||||
import { connectionDiagnosticSections } from "../../diagnostics";
|
||||
|
|
@ -36,7 +36,7 @@ export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel
|
|||
}
|
||||
|
||||
function toolbarThreadRows(input: {
|
||||
threads: readonly Thread[];
|
||||
threads: readonly PanelThread[];
|
||||
activeThreadId: string | null;
|
||||
turnBusy: boolean;
|
||||
archiveConfirmThreadId: string | null;
|
||||
|
|
@ -65,7 +65,6 @@ export function connectionDiagnosticsModel(input: ConnectionDiagnosticsModelInpu
|
|||
connected: input.connected,
|
||||
configuredCommand: input.configuredCommand,
|
||||
initializeResponse: input.state.connection.initializeResponse,
|
||||
activeThreadCreationCliVersion: input.state.activeThread.creationCliVersion,
|
||||
diagnostics: input.state.connection.appServerDiagnostics,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Model } from "../../../generated/app-server/v2/Model";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import type { OpenCodexPanelSnapshot } from "../../../workspace/open-panel-snapshot";
|
||||
import { readRuntimeConfig } from "../../../runtime/config";
|
||||
import { currentModel } from "../../../runtime/effective-settings";
|
||||
|
|
@ -145,11 +145,9 @@ function displayItemsSignature(items: readonly DisplayItem[]): string {
|
|||
return stableSignature(items);
|
||||
}
|
||||
|
||||
function threadListSignature(threads: readonly Thread[]): string {
|
||||
function threadListSignature(threads: readonly PanelThread[]): string {
|
||||
return threads
|
||||
.map((thread) =>
|
||||
signatureParts(thread.id, thread.name, thread.preview, thread.updatedAt, thread.cliVersion, thread.status, thread.gitInfo),
|
||||
)
|
||||
.map((thread) => signatureParts(thread.id, thread.name, thread.preview, thread.updatedAt, thread.archived))
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Notice } from "obsidian";
|
||||
|
||||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import { panelThreadFromAppServerThread } from "../../../app-server/thread-model";
|
||||
import { exportArchivedThreadMarkdown } from "../../../domain/threads/export";
|
||||
import type { ArchiveExportAdapter } from "../../../domain/threads/export";
|
||||
import { inheritedForkThreadName, upsertThread } from "../../../domain/threads/model";
|
||||
|
|
@ -85,7 +86,11 @@ async function archiveThreadOnServer(
|
|||
const settings = host.settings();
|
||||
if (saveMarkdown) {
|
||||
const response = await client.readThread(threadId, true);
|
||||
const result = await exportArchivedThreadMarkdown(response.thread, { ...settings, vaultPath: host.vaultPath }, host.archiveAdapter());
|
||||
const result = await exportArchivedThreadMarkdown(
|
||||
{ ...panelThreadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
|
||||
{ ...settings, vaultPath: host.vaultPath },
|
||||
host.archiveAdapter(),
|
||||
);
|
||||
new Notice(`Saved archived thread to ${result.path}.`);
|
||||
}
|
||||
await client.archiveThread(threadId);
|
||||
|
|
@ -176,9 +181,10 @@ async function rollbackThread(host: ChatThreadActionsHost, threadId: string): Pr
|
|||
try {
|
||||
host.setStatus("Rolling back latest turn...");
|
||||
const response = await client.rollbackThread(threadId);
|
||||
const thread = panelThreadFromAppServerThread(response.thread);
|
||||
dispatch(host, {
|
||||
type: "active-thread/resumed",
|
||||
thread: response.thread,
|
||||
thread,
|
||||
cwd: response.thread.cwd,
|
||||
model: state(host).runtime.activeModel,
|
||||
reasoningEffort: state(host).runtime.activeReasoningEffort,
|
||||
|
|
@ -186,7 +192,7 @@ async function rollbackThread(host: ChatThreadActionsHost, threadId: string): Pr
|
|||
approvalPolicy: state(host).runtime.activeApprovalPolicy,
|
||||
approvalsReviewer: state(host).runtime.activeApprovalsReviewer,
|
||||
activePermissionProfile: state(host).runtime.activePermissionProfile,
|
||||
listedThreads: upsertThread(state(host).threadList.listedThreads, response.thread),
|
||||
listedThreads: upsertThread(state(host).threadList.listedThreads, thread),
|
||||
});
|
||||
dispatch(host, {
|
||||
type: "transcript/items-replaced",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
THREAD_NAMING_CONTEXT_UNAVAILABLE_MESSAGE,
|
||||
type ThreadNamingContext,
|
||||
} from "../../../domain/threads/naming";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import type { Turn } from "../../../generated/app-server/v2/Turn";
|
||||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
import type { ChatAction, ChatState, ChatStateStore } from "../chat-state";
|
||||
|
|
@ -257,7 +257,7 @@ export class ThreadRenameController {
|
|||
return Boolean(this.thread(threadId)?.name?.trim());
|
||||
}
|
||||
|
||||
private thread(threadId: string): Thread | undefined {
|
||||
private thread(threadId: string): PanelThread | undefined {
|
||||
return this.state.threadList.listedThreads.find((item) => item.id === threadId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import { panelThreadFromAppServerThread } from "../../../app-server/thread-model";
|
||||
import type { ThreadTokenUsage } from "../../../generated/app-server/v2/ThreadTokenUsage";
|
||||
import { setActiveThreadTokenUsageAction } from "../chat-state-actions";
|
||||
import { activeThreadId, canSwitchToThread, displayItemsEmpty, listedThreads } from "../chat-state-selectors";
|
||||
|
|
@ -46,7 +47,7 @@ export class ThreadResumeController {
|
|||
try {
|
||||
const response = await client.resumeThread(threadId, this.host.vaultPath);
|
||||
if (this.isStale(resume)) return;
|
||||
this.applyResumedThread(response);
|
||||
this.applyResumedThread(this.panelThreadActivationResponse(response));
|
||||
this.recoverResumedThreadTokenUsage(response.thread.id, response.thread.path, resume);
|
||||
if (response.initialTurnsPage) {
|
||||
this.host.history.applyLatestPage(response.thread.id, response.initialTurnsPage);
|
||||
|
|
@ -85,6 +86,10 @@ export class ThreadResumeController {
|
|||
this.host.refreshLiveState();
|
||||
}
|
||||
|
||||
private panelThreadActivationResponse(response: Awaited<ReturnType<AppServerClient["resumeThread"]>>): ThreadActivationResponse {
|
||||
return { ...response, thread: panelThreadFromAppServerThread(response.thread) };
|
||||
}
|
||||
|
||||
private recoverResumedThreadTokenUsage(threadId: string, path: string | null, resume: ActiveChatResume): void {
|
||||
if (!path || !this.host.recoverTokenUsageFromRollout) return;
|
||||
void this.host
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import type { ReasoningEffort } from "../../../generated/app-server/ReasoningEff
|
|||
import type { ActivePermissionProfile } from "../../../generated/app-server/v2/ActivePermissionProfile";
|
||||
import type { ApprovalsReviewer } from "../../../generated/app-server/v2/ApprovalsReviewer";
|
||||
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import type { ActiveThreadResumedAction } from "../chat-state";
|
||||
import type { DisplayItem } from "../display/types";
|
||||
|
||||
export interface ThreadActivationResponse {
|
||||
thread: Thread;
|
||||
thread: PanelThread;
|
||||
cwd: string;
|
||||
model: string;
|
||||
serviceTier: string | null;
|
||||
|
|
@ -21,7 +21,7 @@ export interface ThreadActivationResponse {
|
|||
|
||||
export interface ResumedThreadActionParams {
|
||||
response: ThreadActivationResponse;
|
||||
listedThreads?: readonly Thread[];
|
||||
listedThreads?: readonly PanelThread[];
|
||||
displayItems?: readonly DisplayItem[];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export function createConversationSurfaceControllerGroup(
|
|||
startNewThread: thread.startNewThread,
|
||||
startThreadForGoal: async (objective) => {
|
||||
const response = await refs.appServerThreads.startThread(objective, { syncGoal: false });
|
||||
return response?.thread.id ?? null;
|
||||
return response?.threadId ?? null;
|
||||
},
|
||||
resumeThread: thread.selectThread,
|
||||
forkThread: (threadId) => refs.threadActions.forkThread(threadId),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
referencedThreadTurns,
|
||||
REFERENCED_THREAD_TURN_LIMIT,
|
||||
} from "../../../domain/threads/reference";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import {
|
||||
executeSlashCommand as runSlashCommand,
|
||||
type SlashCommandExecutionResult,
|
||||
|
|
@ -128,7 +128,7 @@ async function executeSlashCommand(
|
|||
async function referencedThreadInput(
|
||||
host: SlashCommandActionsHost,
|
||||
client: AppServerClient,
|
||||
thread: Thread,
|
||||
thread: PanelThread,
|
||||
message: string,
|
||||
): Promise<ThreadReferenceInput | null> {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ReasoningEffort } from "../../../generated/app-server/ReasoningEffort";
|
||||
import type { Thread } from "../../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../../domain/threads/model";
|
||||
import type { ThreadGoal } from "../../../generated/app-server/v2/ThreadGoal";
|
||||
import type { ThreadGoalStatus } from "../../../generated/app-server/v2/ThreadGoalStatus";
|
||||
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
|
||||
|
|
@ -24,11 +24,11 @@ import {
|
|||
export interface SlashCommandExecutionContext {
|
||||
activeThreadId: string | null;
|
||||
busy: boolean;
|
||||
listedThreads: readonly Thread[];
|
||||
listedThreads: readonly PanelThread[];
|
||||
startNewThread: () => Promise<void>;
|
||||
startThreadForGoal: (objective: string) => Promise<string | null>;
|
||||
resumeThread: (threadId: string) => Promise<void>;
|
||||
referThread: (thread: Thread, message: string) => Promise<ThreadReferenceInput | null>;
|
||||
referThread: (thread: PanelThread, message: string) => Promise<ThreadReferenceInput | null>;
|
||||
forkThread: (threadId: string) => Promise<void>;
|
||||
rollbackThread: (threadId: string) => Promise<void>;
|
||||
compactThread: (threadId: string) => Promise<void>;
|
||||
|
|
@ -392,7 +392,7 @@ function lineToRow(line: string): DisplayDetailMetaRow {
|
|||
return { key: "message", value: line };
|
||||
}
|
||||
|
||||
type ThreadResolution = { ok: true; thread: Thread } | { ok: false; message: string };
|
||||
type ThreadResolution = { ok: true; thread: PanelThread } | { ok: false; message: string };
|
||||
|
||||
function parseReferArgs(args: string): { threadQuery: string; message: string } | null {
|
||||
const parsed = parseThreadAndTextArgs(args);
|
||||
|
|
@ -414,7 +414,7 @@ function parseThreadAndNameArgs(args: string): { threadQuery: string; text: stri
|
|||
return text ? { threadQuery: parsed.threadQuery, text } : null;
|
||||
}
|
||||
|
||||
function resolveThreadArgument(args: string, threads: readonly Thread[]): ThreadResolution {
|
||||
function resolveThreadArgument(args: string, threads: readonly PanelThread[]): ThreadResolution {
|
||||
const query = args.trim();
|
||||
if (!query) {
|
||||
const thread = threads.at(0);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { VIEW_TYPE_CODEX_PANEL } from "../../constants";
|
|||
import type { DisplayDetailSection, DisplayItem } from "./display/types";
|
||||
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
|
||||
import type { Model } from "../../generated/app-server/v2/Model";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../runtime/override-commands";
|
||||
import type { RuntimeSnapshot } from "../../runtime/effective-settings";
|
||||
import { chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } from "./chat-state";
|
||||
|
|
@ -347,7 +347,7 @@ export class CodexChatView extends ItemView {
|
|||
try {
|
||||
await this.controllers.connection.controller.ensureConnected();
|
||||
const response = await this.controllers.appServer.threads.startThread(objective, { syncGoal: false });
|
||||
threadId = response?.thread.id ?? null;
|
||||
threadId = response?.threadId ?? null;
|
||||
} catch (error) {
|
||||
this.controllers.inbound.controller.addSystemMessage(error instanceof Error ? error.message : String(error));
|
||||
this.controllers.render.controller.render();
|
||||
|
|
@ -411,7 +411,7 @@ export class CodexChatView extends ItemView {
|
|||
return this.loadSharedThreadList();
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void {
|
||||
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
|
||||
this.controllers.appServer.threads.applyThreadList(threads);
|
||||
this.refreshTabHeader();
|
||||
this.controllers.render.controller.render();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Notice, Platform, SuggestModal, type App } from "obsidian";
|
||||
|
||||
import { ConnectionManager, StaleConnectionError } from "../../app-server/connection-manager";
|
||||
import { panelThreadsFromAppServerThreads } from "../../app-server/thread-model";
|
||||
import { getThreadTitle } from "../../domain/threads/model";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { CodexPanelSettings } from "../../settings/model";
|
||||
import { shortThreadId } from "../../utils";
|
||||
|
||||
|
|
@ -10,14 +11,14 @@ export interface ThreadPickerHost {
|
|||
readonly app: App;
|
||||
readonly settings: CodexPanelSettings;
|
||||
readonly vaultPath: string;
|
||||
cachedThreadList(): readonly Thread[] | null;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
|
||||
cachedThreadList(): readonly PanelThread[] | null;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
|
||||
openThreadInCurrentView(threadId: string): Promise<void>;
|
||||
openThreadInAvailableView(threadId: string): Promise<void>;
|
||||
}
|
||||
|
||||
interface ThreadSuggestion {
|
||||
thread: Thread;
|
||||
thread: PanelThread;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ export async function openThreadPicker(host: ThreadPickerHost): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
export function threadPickerSuggestions(threads: readonly Thread[], queryText: string): ThreadSuggestion[] {
|
||||
export function threadPickerSuggestions(threads: readonly PanelThread[], queryText: string): ThreadSuggestion[] {
|
||||
const query = queryText.trim().toLowerCase();
|
||||
return [...threads]
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
|
|
@ -78,7 +79,7 @@ export function threadOpenModeFromEvent(evt: MouseEvent | KeyboardEvent): Thread
|
|||
return "current";
|
||||
}
|
||||
|
||||
async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly Thread[]> {
|
||||
async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly PanelThread[]> {
|
||||
const cached = host.cachedThreadList();
|
||||
if (cached) return cached;
|
||||
|
||||
|
|
@ -99,7 +100,7 @@ async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly
|
|||
if (!client) throw new Error("Codex app-server is not connected.");
|
||||
return await host.refreshThreadList(async () => {
|
||||
const response = await client.listThreads(host.vaultPath);
|
||||
return response.data;
|
||||
return panelThreadsFromAppServerThreads(response.data);
|
||||
});
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
|
|
@ -109,7 +110,7 @@ async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly
|
|||
class ThreadPickerModal extends SuggestModal<ThreadSuggestion> {
|
||||
constructor(
|
||||
private readonly host: ThreadPickerHost,
|
||||
private readonly threads: readonly Thread[],
|
||||
private readonly threads: readonly PanelThread[],
|
||||
) {
|
||||
super(host.app);
|
||||
this.limit = MAX_THREAD_PICKER_SUGGESTIONS;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import { getThreadTitle } from "../../domain/threads/model";
|
||||
|
||||
type ThreadsLiveStatus = "needs-input" | "approval" | "running" | "draft" | "offline" | "open";
|
||||
|
|
@ -12,7 +12,7 @@ export interface ThreadsLiveState {
|
|||
}
|
||||
|
||||
export interface ThreadsRowModel {
|
||||
thread: Thread;
|
||||
thread: PanelThread;
|
||||
title: string;
|
||||
live: ThreadsLiveState | null;
|
||||
selected: boolean;
|
||||
|
|
@ -33,7 +33,7 @@ const STATUS_PRIORITY: Record<ThreadsLiveStatus, number> = {
|
|||
};
|
||||
|
||||
export function threadRows(
|
||||
threads: readonly Thread[],
|
||||
threads: readonly PanelThread[],
|
||||
snapshots: OpenCodexPanelSnapshot[],
|
||||
renameStates: ReadonlyMap<string, ThreadsRenameState>,
|
||||
archiveConfirmThreadId: string | null = null,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import { ItemView, Notice, type WorkspaceLeaf } from "obsidian";
|
|||
|
||||
import type { AppServerClient } from "../../app-server/client";
|
||||
import { ConnectionManager, StaleConnectionError } from "../../app-server/connection-manager";
|
||||
import { panelThreadFromAppServerThread, panelThreadsFromAppServerThreads } from "../../app-server/thread-model";
|
||||
import { VIEW_TYPE_CODEX_THREADS } from "../../constants";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../../domain/threads/model";
|
||||
import type { CodexPanelSettings } from "../../settings/model";
|
||||
import { exportArchivedThreadMarkdown } from "../../domain/threads/export";
|
||||
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
|
||||
|
|
@ -38,8 +39,8 @@ export interface CodexThreadsHost {
|
|||
getOpenPanelSnapshots(): OpenCodexPanelSnapshot[];
|
||||
notifyThreadArchived(threadId: string, options?: { closeOpenPanels?: boolean }): void;
|
||||
notifyThreadRenamed(threadId: string, name: string | null): void;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
|
||||
cachedThreadList(): readonly Thread[] | null;
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
|
||||
cachedThreadList(): readonly PanelThread[] | null;
|
||||
}
|
||||
|
||||
type ThreadsViewStatus =
|
||||
|
|
@ -56,7 +57,7 @@ export class CodexThreadsView extends ItemView {
|
|||
private connectionLifecycle: ThreadsViewConnectionLifecycleState = { kind: "idle" };
|
||||
private refreshLifecycle: ThreadsViewRefreshLifecycleState = { kind: "idle" };
|
||||
private status: ThreadsViewStatus = { kind: "idle" };
|
||||
private threads: readonly Thread[] = [];
|
||||
private threads: readonly PanelThread[] = [];
|
||||
private readonly renameStates = new Map<string, ThreadsRenameState>();
|
||||
private archiveConfirmThreadId: string | null = null;
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ export class CodexThreadsView extends ItemView {
|
|||
const threads = await this.plugin.refreshThreadList(async () => {
|
||||
if (!this.client) return [];
|
||||
const response = await this.client.listThreads(this.plugin.vaultPath);
|
||||
return response.data;
|
||||
return panelThreadsFromAppServerThreads(response.data);
|
||||
});
|
||||
if (this.isStaleRefresh(refresh)) return;
|
||||
this.threads = threads;
|
||||
|
|
@ -252,7 +253,7 @@ export class CodexThreadsView extends ItemView {
|
|||
this.scheduleRender();
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void {
|
||||
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
|
||||
this.threads = threads;
|
||||
this.status = threads.length === 0 ? { kind: "empty", message: "No threads" } : { kind: "idle" };
|
||||
this.render();
|
||||
|
|
@ -366,7 +367,7 @@ export class CodexThreadsView extends ItemView {
|
|||
if (saveMarkdown) {
|
||||
const response = await this.client.readThread(threadId, true);
|
||||
const result = await exportArchivedThreadMarkdown(
|
||||
response.thread,
|
||||
{ ...panelThreadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
|
||||
{ ...this.plugin.settings, vaultPath: this.plugin.vaultPath },
|
||||
this.app.vault.adapter,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import type { AppServerClient } from "../app-server/client";
|
||||
import { panelThreadsFromAppServerThreads } from "../app-server/thread-model";
|
||||
import type { HookMetadata } from "../generated/app-server/v2/HookMetadata";
|
||||
import type { Model } from "../generated/app-server/v2/Model";
|
||||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
import { errorMessage } from "../utils";
|
||||
|
||||
export interface LoadedHooks {
|
||||
|
|
@ -14,7 +15,7 @@ export interface LoadedHooks {
|
|||
export interface SettingsDataLoad {
|
||||
models: SettledSettingsData<Model[]>;
|
||||
hooks: SettledSettingsData<LoadedHooks>;
|
||||
archivedThreads: SettledSettingsData<Thread[]>;
|
||||
archivedThreads: SettledSettingsData<PanelThread[]>;
|
||||
}
|
||||
|
||||
type SettledSettingsData<T> =
|
||||
|
|
@ -106,7 +107,7 @@ export async function loadSettingsData(client: AppServerClient, cwd: string): Pr
|
|||
archivedThreadsResult.status === "fulfilled"
|
||||
? {
|
||||
ok: true,
|
||||
data: archivedThreadsResult.value.data,
|
||||
data: panelThreadsFromAppServerThreads(archivedThreadsResult.value.data, { archived: true }),
|
||||
status: `Loaded ${String(archivedThreadsResult.value.data.length)} archived thread${archivedThreadsResult.value.data.length === 1 ? "" : "s"}.`,
|
||||
}
|
||||
: { ok: false, status: `Could not load archived threads: ${errorMessage(archivedThreadsResult.reason)}` },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Setting } from "obsidian";
|
||||
|
||||
import type { HookMetadata } from "../generated/app-server/v2/HookMetadata";
|
||||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
import { archivedThreadDisplayTitle } from "../domain/threads/model";
|
||||
import { shortThreadId } from "../utils";
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ export interface ArchivedThreadSectionState {
|
|||
exportFolderTemplate: string;
|
||||
exportFilenameTemplate: string;
|
||||
exportTags: string;
|
||||
threads: Thread[];
|
||||
threads: PanelThread[];
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
status: string;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { type App, Notice, type Plugin, PluginSettingTab, Setting, setIcon } fro
|
|||
|
||||
import type { AppServerClient } from "../app-server/client";
|
||||
import { withShortLivedAppServerClient } from "../app-server/short-lived-client";
|
||||
import { panelThreadFromAppServerThread } from "../app-server/thread-model";
|
||||
import { DEFAULT_CODEX_PATH } from "../constants";
|
||||
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
|
||||
import type { HookMetadata } from "../generated/app-server/v2/HookMetadata";
|
||||
import type { Model } from "../generated/app-server/v2/Model";
|
||||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
import { findModelByIdOrName, REASONING_EFFORTS, sortedAvailableModels, supportedEffortsForModel } from "../runtime/models";
|
||||
import { archivedThreadDisplayTitle } from "../domain/threads/model";
|
||||
import { errorMessage } from "../utils";
|
||||
|
|
@ -37,7 +38,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
private settingsDataAutoLoadStarted = false;
|
||||
private settingsDynamicOperationId = 0;
|
||||
private settingsDataRefreshLifecycle: SettingsDataRefreshLifecycleState = { kind: "idle" };
|
||||
private archivedThreads: Thread[] = [];
|
||||
private archivedThreads: PanelThread[] = [];
|
||||
private archivedThreadsLifecycle = createSettingsDynamicSectionLifecycle();
|
||||
private hooks: HookMetadata[] = [];
|
||||
private hookWarnings: string[] = [];
|
||||
|
|
@ -480,9 +481,10 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
try {
|
||||
const response = await this.withSettingsConnection((client) => client.unarchiveThread(threadId));
|
||||
this.archivedThreads = this.archivedThreads.filter((thread) => thread.id !== threadId);
|
||||
const restoredThread = panelThreadFromAppServerThread(response.thread);
|
||||
this.archivedThreadsLifecycle = transitionSettingsDynamicSectionLifecycle(this.archivedThreadsLifecycle, {
|
||||
type: "loaded",
|
||||
status: `Restored "${archivedThreadDisplayTitle(response.thread)}".`,
|
||||
status: `Restored "${archivedThreadDisplayTitle(restoredThread)}".`,
|
||||
operationId,
|
||||
});
|
||||
this.plugin.refreshSharedThreadListFromOpenSurface();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { App } from "obsidian";
|
|||
import { VIEW_TYPE_CODEX_THREADS } from "../constants";
|
||||
import { CodexThreadsView } from "../features/threads-view/view";
|
||||
import type { Model } from "../generated/app-server/v2/Model";
|
||||
import type { Thread } from "../generated/app-server/v2/Thread";
|
||||
import type { PanelThread } from "../domain/threads/model";
|
||||
import type { SharedAppServerMetadata } from "../app-server/shared-cache-state";
|
||||
import type { WorkspacePanelCoordinator } from "./panel-coordinator";
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export class ThreadSurfaceCoordinator {
|
|||
if (threadsView) void threadsView.refresh();
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void {
|
||||
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
|
||||
for (const view of this.options.panels.panelViews()) {
|
||||
view.applyThreadListSnapshot(threads);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
cachedSharedThreadList,
|
||||
createSharedAppServerState,
|
||||
} from "../../src/app-server/shared-cache-state";
|
||||
import type { PanelThread } from "../../src/domain/threads/model";
|
||||
import type { Model } from "../../src/generated/app-server/v2/Model";
|
||||
import type { Thread } from "../../src/generated/app-server/v2/Thread";
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ describe("shared app-server cache state", () => {
|
|||
const cachedThreads = cachedSharedThreadList(threadState);
|
||||
expect(cachedThreads?.map((thread) => thread.id)).toEqual(["thread-1"]);
|
||||
|
||||
const mutableCachedThreads = cachedThreads as Thread[];
|
||||
const mutableCachedThreads = cachedThreads as PanelThread[];
|
||||
mutableCachedThreads.push(threadFixture("thread-3"));
|
||||
expect(cachedSharedThreadList(threadState)?.map((thread) => thread.id)).toEqual(["thread-1"]);
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ describe("shared app-server cache state", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function threadFixture(id: string): Thread {
|
||||
function threadFixture(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -71,6 +72,7 @@ function threadFixture(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe("thread archive export", () => {
|
|||
thread({
|
||||
id: "thread-12345678",
|
||||
name: "Exported thread",
|
||||
archived: false,
|
||||
turns: [
|
||||
turn(
|
||||
[
|
||||
|
|
@ -267,7 +268,7 @@ class MemoryAdapter implements ArchiveExportAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "019e0182-cb70-7a72-ab48-8bc9d0b0d781",
|
||||
sessionId: "019e0182-cb70-7a72-ab48-8bc9d0b0d781",
|
||||
|
|
@ -288,6 +289,7 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
referencedThreadTurns,
|
||||
} from "../../../src/domain/threads/reference";
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "019abcde-0000-7000-8000-000000000001",
|
||||
sessionId: "session-1",
|
||||
|
|
@ -30,9 +30,10 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: "参照元",
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
||||
function turn(id: string, startedAt: number, items: Turn["items"]): Turn {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ describe("thread helpers", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "thread-1",
|
||||
sessionId: "session-1",
|
||||
|
|
@ -69,7 +69,8 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { panelThreadFromAppServerThread } from "../../../src/app-server/thread-model";
|
||||
import { createChatAppServerDiagnosticsActions } from "../../../src/features/chat/app-server/diagnostics-actions";
|
||||
import { createChatAppServerMetadataActions } from "../../../src/features/chat/app-server/metadata-actions";
|
||||
import { createChatAppServerThreadActions } from "../../../src/features/chat/app-server/thread-actions";
|
||||
|
|
@ -15,10 +16,11 @@ describe("chat app-server controllers", () => {
|
|||
it("publishes newly started threads before the first turn completes", async () => {
|
||||
const state = createChatState();
|
||||
const existing = threadFixture("existing");
|
||||
state.threadList.listedThreads = [existing];
|
||||
state.threadList.listedThreads = [panelThreadFromAppServerThread(existing)];
|
||||
const stateStore = createChatStateStore(state);
|
||||
const started = threadFixture("started");
|
||||
const optimistic = { ...started, preview: "first prompt" };
|
||||
const optimistic = panelThreadFromAppServerThread({ ...started, preview: "first prompt" });
|
||||
const existingPanelThread = panelThreadFromAppServerThread(existing);
|
||||
const publishThreadList = vi.fn();
|
||||
const syncThreadGoal = vi.fn();
|
||||
const client = {
|
||||
|
|
@ -45,8 +47,8 @@ describe("chat app-server controllers", () => {
|
|||
|
||||
await controller.startThread("first prompt");
|
||||
|
||||
expect(stateStore.getState().threadList.listedThreads).toEqual([optimistic, existing]);
|
||||
expect(publishThreadList).toHaveBeenCalledWith([optimistic, existing]);
|
||||
expect(stateStore.getState().threadList.listedThreads).toEqual([optimistic, existingPanelThread]);
|
||||
expect(publishThreadList).toHaveBeenCalledWith([optimistic, existingPanelThread]);
|
||||
expect(syncThreadGoal).toHaveBeenCalledWith("started");
|
||||
});
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ describe("chat app-server controllers", () => {
|
|||
|
||||
await controller.startThread("local preview");
|
||||
|
||||
expect(publishThreadList).toHaveBeenCalledWith([started]);
|
||||
expect(publishThreadList).toHaveBeenCalledWith([panelThreadFromAppServerThread(started)]);
|
||||
});
|
||||
|
||||
it("reuses cached app-server metadata for deferred diagnostics", async () => {
|
||||
|
|
|
|||
|
|
@ -554,7 +554,7 @@ function goal(threadId: string): ThreadGoal {
|
|||
};
|
||||
}
|
||||
|
||||
export function thread(id: string): Thread {
|
||||
export function thread(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -575,6 +575,7 @@ export function thread(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function expectPresent<T>(value: T | null | undefined): T {
|
|||
return value;
|
||||
}
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "019abcde-0000-7000-8000-000000000001",
|
||||
sessionId: "session-1",
|
||||
|
|
@ -40,9 +40,10 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
||||
function model(name: string, efforts: ReasoningEffort[], overrides: Partial<Model> = {}): Model {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ describe("connection diagnostics", () => {
|
|||
platformFamily: "unix",
|
||||
platformOs: "macos",
|
||||
},
|
||||
activeThreadCreationCliVersion: "0.130.0",
|
||||
diagnostics,
|
||||
});
|
||||
|
||||
|
|
@ -46,7 +45,6 @@ describe("connection diagnostics", () => {
|
|||
expect(rows.map((row) => `${row.label}: ${row.value}`)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"connection: connected",
|
||||
"thread created by CLI: 0.130.0",
|
||||
"model/list: ok (12 models)",
|
||||
"skills/list: failed - unknown method skills/list",
|
||||
"mcp docs: ready - auth notLoggedIn",
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
|
||||
it("hides persisted /refer context in displayed user messages", () => {
|
||||
const text = referencedThreadPrompt(
|
||||
{ id: "thread-reference", name: "参照元", preview: "", turns: [] } as unknown as Thread,
|
||||
{ id: "thread-reference", name: "参照元", preview: "", archived: false } as Thread & { archived: boolean },
|
||||
[
|
||||
{ userText: "元の依頼", assistantText: "元の回答" },
|
||||
{ userText: "次の依頼", assistantText: "次の回答" },
|
||||
|
|
|
|||
|
|
@ -953,7 +953,6 @@ describe("ChatInboundController", () => {
|
|||
state.turn.lifecycle = { kind: "running", turnId: "turn-active" };
|
||||
state.runtime.activeModel = "gpt-5.5";
|
||||
state.runtime.activeServiceTier = "fast";
|
||||
state.activeThread.creationCliVersion = "codex-cli 1.0.0";
|
||||
state.activeThread.tokenUsage = {
|
||||
last: { inputTokens: 1, cachedInputTokens: 0, outputTokens: 2, reasoningOutputTokens: 0, totalTokens: 3 },
|
||||
total: { inputTokens: 1, cachedInputTokens: 0, outputTokens: 2, reasoningOutputTokens: 0, totalTokens: 3 },
|
||||
|
|
@ -1001,7 +1000,6 @@ describe("ChatInboundController", () => {
|
|||
expect(activeTurnId(state)).toBeNull();
|
||||
expect(state.runtime.activeModel).toBeNull();
|
||||
expect(state.runtime.activeServiceTier).toBeNull();
|
||||
expect(state.activeThread.creationCliVersion).toBeNull();
|
||||
expect(state.activeThread.tokenUsage).toBeNull();
|
||||
expect(state.transcript.historyCursor).toBeNull();
|
||||
expect(state.transcript.loadingHistory).toBe(false);
|
||||
|
|
@ -1712,7 +1710,7 @@ function userInputRequest(id: number): ServerRequest {
|
|||
};
|
||||
}
|
||||
|
||||
function thread(id: string, cwd: string): Thread {
|
||||
function thread(id: string, cwd: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -1733,6 +1731,7 @@ function thread(id: string, cwd: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { RestoredThreadController } from "../../../../src/features/chat/thr
|
|||
import type { RestoredThreadPlaceholderState } from "../../../../src/features/chat/panel/lifecycle";
|
||||
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
|
||||
|
||||
function thread(id: string, name: string | null = null): Thread {
|
||||
function thread(id: string, name: string | null = null): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -27,6 +27,7 @@ function thread(id: string, name: string | null = null): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ function fakeClient(options: { setThreadName?: ReturnType<typeof vi.fn> } = {}):
|
|||
} as unknown as AppServerClient;
|
||||
}
|
||||
|
||||
function threadFixture(id: string): Thread {
|
||||
function threadFixture(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -168,6 +168,7 @@ function threadFixture(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ import { describe, expect, it, vi } from "vitest";
|
|||
import type { AppServerClient } from "../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/chat-state";
|
||||
import type { RestoredThreadController } from "../../../../src/features/chat/threads/restored-thread-controller";
|
||||
import type { ThreadActivationResponse } from "../../../../src/features/chat/threads/thread-resume";
|
||||
import { ThreadResumeController } from "../../../../src/features/chat/threads/thread-resume-controller";
|
||||
import type { ThreadHistoryController } from "../../../../src/features/chat/threads/thread-history-controller";
|
||||
import { ChatResumeWorkTracker } from "../../../../src/features/chat/panel/lifecycle";
|
||||
import type { ThreadResumeResponse } from "../../../../src/generated/app-server/v2/ThreadResumeResponse";
|
||||
import type { ThreadItem } from "../../../../src/generated/app-server/v2/ThreadItem";
|
||||
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
|
||||
import type { ThreadTokenUsage } from "../../../../src/generated/app-server/v2/ThreadTokenUsage";
|
||||
import type { Turn } from "../../../../src/generated/app-server/v2/Turn";
|
||||
|
||||
function thread(id: string): Thread {
|
||||
function thread(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -33,25 +33,31 @@ function thread(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
||||
function activation(threadId: string): ThreadActivationResponse {
|
||||
function activation(threadId: string): ThreadResumeResponse {
|
||||
return {
|
||||
thread: thread(threadId),
|
||||
cwd: "/vault",
|
||||
model: "gpt-test",
|
||||
modelProvider: "openai",
|
||||
serviceTier: null,
|
||||
approvalPolicy: null,
|
||||
approvalsReviewer: null,
|
||||
runtimeWorkspaceRoots: [],
|
||||
instructionSources: [],
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "user",
|
||||
sandbox: { type: "readOnly", networkAccess: false },
|
||||
activePermissionProfile: null,
|
||||
reasoningEffort: null,
|
||||
initialTurnsPage: null,
|
||||
};
|
||||
}
|
||||
|
||||
function createController(
|
||||
response: ThreadActivationResponse = activation("thread"),
|
||||
response: ThreadResumeResponse = activation("thread"),
|
||||
overrides: Partial<ConstructorParameters<typeof ThreadResumeController>[0]> = {},
|
||||
) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
|
|
@ -176,7 +182,7 @@ describe("ThreadResumeController", () => {
|
|||
await controller.resumeThread("thread");
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
thread: second.thread,
|
||||
thread: { ...second.thread, archived: false },
|
||||
cwd: "/vault",
|
||||
model: null,
|
||||
reasoningEffort: null,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ describe("chat thread resume helpers", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function responseFixture(thread: Thread): ThreadResumeResponse {
|
||||
function responseFixture(thread: Thread & { archived: boolean }): ThreadResumeResponse & { thread: Thread & { archived: boolean } } {
|
||||
return {
|
||||
thread,
|
||||
model: "gpt-5.5",
|
||||
|
|
@ -64,7 +64,7 @@ function responseFixture(thread: Thread): ThreadResumeResponse {
|
|||
};
|
||||
}
|
||||
|
||||
function threadFixture(id: string, name: string): Thread {
|
||||
function threadFixture(id: string, name: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -85,6 +85,7 @@ function threadFixture(id: string, name: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { createChatState, createChatStateStore } from "../../../../src/features/
|
|||
import { createComposerSubmissionActions } from "../../../../src/features/chat/turns/composer-submission-actions";
|
||||
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
|
||||
|
||||
function thread(id: string): Thread {
|
||||
function thread(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -26,6 +26,7 @@ function thread(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import type { UserInput } from "../../../../src/generated/app-server/v2/UserInpu
|
|||
|
||||
const textInput = (text: string): UserInput[] => [{ type: "text", text, text_elements: [] }];
|
||||
|
||||
function thread(id: string, name: string | null = null): Thread {
|
||||
function thread(id: string, name: string | null = null): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -36,6 +36,7 @@ function thread(id: string, name: string | null = null): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function context(overrides: Partial<SlashCommandExecutionContext> = {}): SlashCo
|
|||
};
|
||||
}
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "thread-1",
|
||||
sessionId: "session-1",
|
||||
|
|
@ -64,9 +64,10 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
||||
function goal(overrides: Partial<ThreadGoal> = {}): ThreadGoal {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type { UserInput } from "../../../../src/generated/app-server/v2/UserInpu
|
|||
|
||||
const textInput = (text: string): UserInput[] => [{ type: "text", text, text_elements: [] }];
|
||||
|
||||
function thread(id: string): Thread {
|
||||
function thread(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: id,
|
||||
|
|
@ -39,6 +39,7 @@ function thread(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
|
||||
import type { CodexChatHost } from "../../../src/features/chat/view";
|
||||
import { createAppServerDiagnostics } from "../../../src/app-server/compatibility";
|
||||
import { panelThreadFromAppServerThread } from "../../../src/app-server/thread-model";
|
||||
import { createChatState, type ChatState } from "../../../src/features/chat/chat-state";
|
||||
import { composerSlotSnapshot } from "../../../src/features/chat/panel/snapshot";
|
||||
import type { ServerNotification } from "../../../src/generated/app-server/ServerNotification";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import { notices } from "../../mocks/obsidian";
|
||||
import { deferred, waitForAsyncWork } from "../../support/async";
|
||||
import { installObsidianDomShims } from "../../support/dom";
|
||||
|
|
@ -156,7 +158,9 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
await view.connect();
|
||||
|
||||
expect(refreshThreadList).toHaveBeenCalledOnce();
|
||||
expect((view as unknown as { state: { threadList: { listedThreads: unknown[] } } }).state.threadList.listedThreads).toEqual(threads);
|
||||
expect((view as unknown as { state: { threadList: { listedThreads: unknown[] } } }).state.threadList.listedThreads).toEqual(
|
||||
threads.map((thread) => panelThreadFromAppServerThread(thread)),
|
||||
);
|
||||
});
|
||||
|
||||
it("publishes app-server metadata after connecting", async () => {
|
||||
|
|
@ -386,7 +390,7 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
expect(client.readAccountRateLimits).toHaveBeenCalledOnce();
|
||||
expect(client.listThreads).toHaveBeenCalledWith("/vault");
|
||||
expect((view as unknown as { state: { threadList: { listedThreads: unknown[] } } }).state.threadList.listedThreads).toEqual([
|
||||
threadFixture("thread-1"),
|
||||
panelThreadFromAppServerThread(threadFixture("thread-1")),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -1062,7 +1066,7 @@ function resumedThread(threadId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
function threadFixture(threadId: string) {
|
||||
function threadFixture(threadId: string): Thread {
|
||||
return {
|
||||
id: threadId,
|
||||
sessionId: "session",
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ function effectiveConfigFixture(config: Record<string, unknown>): ConfigReadResp
|
|||
};
|
||||
}
|
||||
|
||||
function threadFixture(id: string, name: string | null): Thread {
|
||||
function threadFixture(id: string, name: string | null): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -258,6 +258,7 @@ function threadFixture(id: string, name: string | null): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name,
|
||||
archived: false,
|
||||
turns: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ describe("threadOpenModeFromEvent", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function thread(options: Partial<Thread> & { id: string }): Thread {
|
||||
function thread(options: Partial<Thread & { archived: boolean }> & { id: string }): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: options.id,
|
||||
sessionId: "session",
|
||||
|
|
@ -59,6 +59,7 @@ function thread(options: Partial<Thread> & { id: string }): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: options.name ?? null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function openPanelSnapshot(
|
|||
};
|
||||
}
|
||||
|
||||
function threadFixture(overrides: Partial<Thread> = {}): Thread {
|
||||
function threadFixture(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "thread",
|
||||
sessionId: "session",
|
||||
|
|
@ -66,6 +66,7 @@ function threadFixture(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -407,10 +407,10 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
|
||||
it("single-flights shared thread list refreshes and caches successful results", async () => {
|
||||
const plugin = await pluginWithLeaves([]);
|
||||
let resolveThreads!: (threads: Thread[]) => void;
|
||||
let resolveThreads!: (threads: (Thread & { archived: boolean })[]) => void;
|
||||
const fetchThreads = vi.fn(
|
||||
() =>
|
||||
new Promise<Thread[]>((resolve) => {
|
||||
new Promise<(Thread & { archived: boolean })[]>((resolve) => {
|
||||
resolveThreads = resolve;
|
||||
}),
|
||||
);
|
||||
|
|
@ -570,7 +570,7 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) {
|
|||
);
|
||||
}
|
||||
|
||||
function thread(id: string): Thread {
|
||||
function thread(id: string): Thread & { archived: boolean } {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
@ -591,8 +591,9 @@ function thread(id: string): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
} as Thread;
|
||||
} as Thread & { archived: boolean };
|
||||
}
|
||||
|
||||
function panelSnapshot(
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ describe("settings tab", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread & { archived: boolean } {
|
||||
return {
|
||||
id: "019e0182-cb70-7a72-ab48-8bc9d0b0d781",
|
||||
sessionId: "019e0182-cb70-7a72-ab48-8bc9d0b0d781",
|
||||
|
|
@ -291,6 +291,7 @@ function thread(overrides: Partial<Thread> = {}): Thread {
|
|||
agentRole: null,
|
||||
gitInfo: null,
|
||||
name: null,
|
||||
archived: false,
|
||||
turns: [],
|
||||
...overrides,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue