From fd5ccb459892f17a5bb7fbcd2953815db66d4951 Mon Sep 17 00:00:00 2001 From: murashit Date: Fri, 29 May 2026 05:39:33 +0900 Subject: [PATCH] Reuse thread activation action --- .../chat/chat-app-server-controller.ts | 15 ++---------- src/features/chat/thread-resume.ts | 24 +++++++++++++++---- tests/features/chat/thread-resume.test.ts | 16 +++++++++++++ 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/features/chat/chat-app-server-controller.ts b/src/features/chat/chat-app-server-controller.ts index 9d09aa40..7f540677 100644 --- a/src/features/chat/chat-app-server-controller.ts +++ b/src/features/chat/chat-app-server-controller.ts @@ -6,7 +6,6 @@ import { upsertMcpServerDiagnostic, type CapabilityProbeMethod, } from "../../app-server/compatibility"; -import { parseServiceTier } from "../../app-server/service-tier"; import type { McpServerStatus } from "../../generated/app-server/v2/McpServerStatus"; import type { Model } from "../../generated/app-server/v2/Model"; import type { RateLimitSnapshot } from "../../generated/app-server/v2/RateLimitSnapshot"; @@ -14,6 +13,7 @@ import type { SkillMetadata } from "../../generated/app-server/v2/SkillMetadata" import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state"; import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../../runtime/state"; import type { ChatAction, ChatState, ChatStateStore } from "./chat-state"; +import { resumedThreadAction } from "./thread-resume"; export interface ChatAppServerControllerHost { stateStore: ChatStateStore; @@ -99,18 +99,7 @@ export class ChatAppServerController { if (!client) return null; const serviceTier = requestedOrConfiguredServiceTier(this.host.runtimeSnapshot()); const response = await client.startThread(this.host.vaultPath, serviceTier); - 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, - forceMessagesToBottom: true, - }); + this.dispatch(resumedThreadAction({ response, forceMessagesToBottom: true })); this.host.forceMessagesToBottom(); return response; } diff --git a/src/features/chat/thread-resume.ts b/src/features/chat/thread-resume.ts index 2b3540da..c8e1c846 100644 --- a/src/features/chat/thread-resume.ts +++ b/src/features/chat/thread-resume.ts @@ -1,14 +1,29 @@ import { parseServiceTier } from "../../app-server/service-tier"; import { upsertThread } from "../../domain/threads/model"; +import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort"; +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 { ThreadResumeResponse } from "../../generated/app-server/v2/ThreadResumeResponse"; import type { ChatAction } from "./chat-state"; import type { DisplayItem } from "./display/types"; +export interface ThreadActivationResponse { + thread: Thread; + cwd: string; + model: string; + serviceTier: string | null; + approvalPolicy: AskForApproval | null; + approvalsReviewer: ApprovalsReviewer | null; + activePermissionProfile: ActivePermissionProfile | null; + reasoningEffort: ReasoningEffort | null; +} + export interface ResumedThreadActionParams { - response: ThreadResumeResponse; - listedThreads: readonly Thread[]; + response: ThreadActivationResponse; + listedThreads?: readonly Thread[]; displayItems?: readonly DisplayItem[]; + forceMessagesToBottom?: boolean; } export function resumedThreadAction(params: ResumedThreadActionParams): Extract { @@ -24,6 +39,7 @@ export function resumedThreadAction(params: ResumedThreadActionParams): Extract< approvalsReviewer: response.approvalsReviewer, activePermissionProfile: response.activePermissionProfile, ...(params.displayItems ? { displayItems: params.displayItems } : {}), - listedThreads: upsertThread(params.listedThreads, response.thread), + ...(params.listedThreads ? { listedThreads: upsertThread(params.listedThreads, response.thread) } : {}), + ...(params.forceMessagesToBottom !== undefined ? { forceMessagesToBottom: params.forceMessagesToBottom } : {}), }; } diff --git a/tests/features/chat/thread-resume.test.ts b/tests/features/chat/thread-resume.test.ts index d43f6480..c0ca13cd 100644 --- a/tests/features/chat/thread-resume.test.ts +++ b/tests/features/chat/thread-resume.test.ts @@ -30,6 +30,22 @@ describe("chat thread resume helpers", () => { }); expect(action.listedThreads?.map((thread) => thread.id)).toEqual(["thread", "existing"]); }); + + it("can build thread start actions without mutating the thread list", () => { + const resumed = threadFixture("thread", "Started"); + + const action = resumedThreadAction({ + response: responseFixture(resumed), + forceMessagesToBottom: true, + }); + + expect(action).toMatchObject({ + type: "thread/resumed", + thread: resumed, + forceMessagesToBottom: true, + }); + expect(action.listedThreads).toBeUndefined(); + }); }); function responseFixture(thread: Thread): ThreadResumeResponse {