diff --git a/src/features/chat/request-state.ts b/src/features/chat/request-state.ts index 2271c224..2624dfc4 100644 --- a/src/features/chat/request-state.ts +++ b/src/features/chat/request-state.ts @@ -40,6 +40,14 @@ export function pendingRequestsSignature( }); } +export function pendingRequestFocusSignature(approvals: readonly PendingApproval[], inputs: readonly PendingUserInput[]): string { + if (approvals.length === 0 && inputs.length === 0) return ""; + return JSON.stringify({ + approvals: approvals.map((approval) => ({ id: approval.requestId, method: approval.method })), + inputs: inputs.map((input) => ({ id: input.requestId, method: input.method })), + }); +} + export function clearUserInputDrafts(drafts: Map, input: PendingUserInput): void { for (const question of input.params.questions) { drafts.delete(userInputDraftKey(input.requestId, question.id)); diff --git a/src/features/chat/user-input/model.ts b/src/features/chat/user-input/model.ts index 09895569..5e9a0cfa 100644 --- a/src/features/chat/user-input/model.ts +++ b/src/features/chat/user-input/model.ts @@ -3,6 +3,7 @@ import type { ServerRequest } from "../../../generated/app-server/ServerRequest" import type { ToolRequestUserInputParams } from "../../../generated/app-server/v2/ToolRequestUserInputParams"; import type { ToolRequestUserInputQuestion } from "../../../generated/app-server/v2/ToolRequestUserInputQuestion"; import type { ToolRequestUserInputResponse } from "../../../generated/app-server/v2/ToolRequestUserInputResponse"; +import { userInputDraftKey } from "../request-state"; export type UserInputRequest = Extract; @@ -37,3 +38,12 @@ export function userInputResponse(input: PendingUserInput, answers: Record): Record { + return Object.fromEntries( + input.params.questions.map((question) => [ + question.id, + drafts.get(userInputDraftKey(input.requestId, question.id)) ?? questionDefaultAnswer(question), + ]), + ); +} diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index d7bb8696..93dd15dc 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -24,9 +24,14 @@ import { mcpStatusLines } from "./mcp-status"; import { ChatAppServerController } from "./chat-app-server-controller"; import { ThreadHistoryLoader } from "./thread-history"; import { ThreadRenameController } from "./thread-rename"; -import { pendingRequestsSignature as requestStateSignature, userInputDraftKey, userInputOtherDraftKey } from "./request-state"; +import { + pendingRequestFocusSignature, + pendingRequestsSignature as requestStateSignature, + userInputDraftKey, + userInputOtherDraftKey, +} from "./request-state"; import type { CodexPanelSettings } from "../../settings/model"; -import { questionDefaultAnswer, type PendingUserInput } from "./user-input/model"; +import { answersForPendingUserInput, type PendingUserInput } from "./user-input/model"; import { ChatComposerController } from "./chat-composer-controller"; import { activeTurnId, @@ -1382,19 +1387,11 @@ export class CodexChatView extends ItemView { } private pendingRequestFocusSignature(): string { - const approvals = this.state.approvals.map((approval) => ({ id: approval.requestId, method: approval.method })); - const inputs = this.state.pendingUserInputs.map((input) => ({ id: input.requestId, method: input.method })); - if (approvals.length === 0 && inputs.length === 0) return ""; - return JSON.stringify({ approvals, inputs }); + return pendingRequestFocusSignature(this.state.approvals, this.state.pendingUserInputs); } private answersForUserInput(input: PendingUserInput): Record { - return Object.fromEntries( - input.params.questions.map((question) => [ - question.id, - this.state.userInputDrafts.get(userInputDraftKey(input.requestId, question.id)) ?? questionDefaultAnswer(question), - ]), - ); + return answersForPendingUserInput(input, this.state.userInputDrafts); } private queueMessagesBottomScroll(): void { diff --git a/tests/features/chat/user-input/user-input.test.ts b/tests/features/chat/user-input/user-input.test.ts index 4a3ee40c..c7828f7a 100644 --- a/tests/features/chat/user-input/user-input.test.ts +++ b/tests/features/chat/user-input/user-input.test.ts @@ -1,7 +1,12 @@ import { describe, expect, it } from "vitest"; -import { questionDefaultAnswer, toPendingUserInput, userInputResponse } from "../../../../src/features/chat/user-input/model"; -import { pendingRequestsSignature } from "../../../../src/features/chat/request-state"; +import { + answersForPendingUserInput, + questionDefaultAnswer, + toPendingUserInput, + userInputResponse, +} from "../../../../src/features/chat/user-input/model"; +import { pendingRequestFocusSignature, pendingRequestsSignature } from "../../../../src/features/chat/request-state"; import type { ServerRequest } from "../../../../src/generated/app-server/ServerRequest"; function expectPresent(value: T | null | undefined): T { @@ -34,6 +39,8 @@ describe("user input model", () => { const input = expectPresent(toPendingUserInput(request)); expect(input).toMatchObject({ requestId: 7, method: "item/tool/requestUserInput" }); expect(questionDefaultAnswer(expectPresent(request.params.questions[0]))).toBe("Recommended"); + expect(answersForPendingUserInput(input, new Map())).toEqual({ direction: "Recommended" }); + expect(answersForPendingUserInput(input, new Map([["7:direction", "Left"]]))).toEqual({ direction: "Left" }); expect(userInputResponse(input, { direction: "Recommended" })).toEqual({ answers: { direction: { answers: ["Recommended"] } }, }); @@ -67,6 +74,13 @@ describe("user input model", () => { ]); expect(pendingRequestsSignature([], [], drafts)).toBe(""); + expect(pendingRequestFocusSignature([], [])).toBe(""); + expect(pendingRequestFocusSignature([], [input])).toBe( + JSON.stringify({ + approvals: [], + inputs: [{ id: 7, method: "item/tool/requestUserInput" }], + }), + ); expect(pendingRequestsSignature([], [input], drafts)).toBe( JSON.stringify({ approvals: [],