Extract thread resume action

This commit is contained in:
murashit 2026-05-29 05:37:42 +09:00
parent 6b0707112e
commit 89a5be5774
3 changed files with 109 additions and 17 deletions

View file

@ -0,0 +1,29 @@
import { parseServiceTier } from "../../app-server/service-tier";
import { upsertThread } from "../../domain/threads/model";
import type { Thread } from "../../generated/app-server/v2/Thread";
import type { ThreadResumeResponse } from "../../generated/app-server/v2/ThreadResumeResponse";
import type { ChatAction } from "./chat-state";
import type { DisplayItem } from "./display/types";
export interface ResumedThreadActionParams {
response: ThreadResumeResponse;
listedThreads: readonly Thread[];
displayItems?: readonly DisplayItem[];
}
export function resumedThreadAction(params: ResumedThreadActionParams): Extract<ChatAction, { type: "thread/resumed" }> {
const { response } = params;
return {
type: "thread/resumed",
thread: response.thread,
cwd: response.cwd,
model: response.model,
reasoningEffort: response.reasoningEffort,
serviceTier: parseServiceTier(response.serviceTier),
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
activePermissionProfile: response.activePermissionProfile,
...(params.displayItems ? { displayItems: params.displayItems } : {}),
listedThreads: upsertThread(params.listedThreads, response.thread),
};
}

View file

@ -2,7 +2,6 @@ import { ItemView, Notice, type ViewStateResult, type WorkspaceLeaf } from "obsi
import type { AppServerClient } from "../../app-server/client";
import { ConnectionManager, StaleConnectionError } from "../../app-server/connection-manager";
import { parseServiceTier } from "../../app-server/service-tier";
import type { ApprovalAction, PendingApproval } from "./approvals/model";
import type { SlashCommandName } from "./composer/slash-commands";
import { parseSlashCommand } from "./composer/suggestions";
@ -12,7 +11,6 @@ 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 { ThreadResumeResponse } from "../../generated/app-server/v2/ThreadResumeResponse";
import type { UserInput } from "../../generated/app-server/v2/UserInput";
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../runtime/collaboration-mode";
import { ChatController } from "./chat-controller";
@ -40,7 +38,7 @@ import {
type ChatAction,
type ChatState,
} from "./chat-state";
import { codexPanelDisplayTitle, explicitThreadName, getThreadTitle, upsertThread } from "../../domain/threads/model";
import { codexPanelDisplayTitle, explicitThreadName, getThreadTitle } from "../../domain/threads/model";
import {
referencedThreadDisplay,
referencedThreadPrompt,
@ -92,6 +90,7 @@ import {
optimisticTurnStart,
shouldAcknowledgeTurnStart,
} from "./turn-submission";
import { resumedThreadAction, type ResumedThreadActionParams } from "./thread-resume";
export interface CodexChatHost {
readonly settings: CodexPanelSettings;
@ -671,20 +670,10 @@ export class CodexChatView extends ItemView {
}
}
private applyResumedThread(response: ThreadResumeResponse): void {
this.dispatch({
type: "thread/resumed",
thread: response.thread,
cwd: response.cwd,
model: response.model,
reasoningEffort: response.reasoningEffort,
serviceTier: parseServiceTier(response.serviceTier),
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
activePermissionProfile: response.activePermissionProfile,
displayItems: [this.systemItem("Loading thread...")],
listedThreads: upsertThread(this.state.listedThreads, response.thread),
});
private applyResumedThread(response: ResumedThreadActionParams["response"]): void {
this.dispatch(
resumedThreadAction({ response, listedThreads: this.state.listedThreads, displayItems: [this.systemItem("Loading thread...")] }),
);
this.clearRestoredThreadLifecycle();
this.clearDeferredRestoredThreadHydration();
this.threadRename.resetThreadTurnPresence(false);

View file

@ -0,0 +1,74 @@
import { describe, expect, it } from "vitest";
import { resumedThreadAction } from "../../../src/features/chat/thread-resume";
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
import type { ThreadResumeResponse } from "../../../src/generated/app-server/v2/ThreadResumeResponse";
describe("chat thread resume helpers", () => {
it("builds thread resumed actions from response snapshots", () => {
const existing = threadFixture("existing", "Existing");
const resumed = threadFixture("thread", "Resumed");
const loading = { id: "loading", kind: "system" as const, role: "system" as const, text: "Loading thread..." };
const action = resumedThreadAction({
response: responseFixture(resumed),
listedThreads: [existing],
displayItems: [loading],
});
expect(action).toMatchObject({
type: "thread/resumed",
thread: resumed,
cwd: "/vault",
model: "gpt-5.5",
reasoningEffort: "high",
serviceTier: "fast",
approvalPolicy: "on-request",
approvalsReviewer: "user",
activePermissionProfile: null,
displayItems: [loading],
});
expect(action.listedThreads?.map((thread) => thread.id)).toEqual(["thread", "existing"]);
});
});
function responseFixture(thread: Thread): ThreadResumeResponse {
return {
thread,
model: "gpt-5.5",
modelProvider: "openai",
serviceTier: "fast",
cwd: "/vault",
runtimeWorkspaceRoots: [],
instructionSources: [],
approvalPolicy: "on-request",
approvalsReviewer: "user",
sandbox: { type: "readOnly", networkAccess: false },
activePermissionProfile: null,
reasoningEffort: "high",
};
}
function threadFixture(id: string, name: string): Thread {
return {
id,
sessionId: "session",
forkedFromId: null,
preview: "",
ephemeral: false,
modelProvider: "openai",
createdAt: 1,
updatedAt: 1,
status: { type: "idle" },
path: null,
cwd: "/vault",
cliVersion: "0.0.0",
source: "appServer",
threadSource: null,
agentNickname: null,
agentRole: null,
gitInfo: null,
name,
turns: [],
};
}