From 9d0820e3e5b47fc1d6c8b4207bc3d21dd91c9e06 Mon Sep 17 00:00:00 2001 From: Tang Ziya Date: Mon, 7 Sep 2026 09:56:22 +0800 Subject: [PATCH] feat: add title generation opt-out --- README.md | 1 + readme-dev.md | 1 + src/TitleGenerator.ts | 1 + src/__tests__/TitleGenerator.test.ts | 53 ++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 src/__tests__/TitleGenerator.test.ts diff --git a/README.md b/README.md index 88a2ad51..9793c34d 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ The adapter advertises ACP auth methods during initialization. Clients can authe - `INITIAL_AGENT_MODE` - initial mode id: `read-only`, `agent`, or `agent-full-access`. - `NO_BROWSER` - hide browser-based ChatGPT auth when set. - `APP_SERVER_LOGS` - directory for adapter logs. +- `ACP_DISABLE_TITLE_GENERATION` - set to `1` to skip the adapter's extra model call for session titles. Applies to every session in this adapter process; unset or other values retain automatic generation. Existing titles, prompt-derived fallback titles, and `/rename` remain available. ## Development diff --git a/readme-dev.md b/readme-dev.md index bc147807..556eab1f 100644 --- a/readme-dev.md +++ b/readme-dev.md @@ -12,6 +12,7 @@ Set `CODEX_PATH` to run a different Codex binary; versions other than the one sp - `INITIAL_AGENT_MODE` - initial mode id: `read-only`, `agent`, or `agent-full-access`. - `NO_BROWSER` - hide browser-based ChatGPT auth when set. - `APP_SERVER_LOGS` - directory for adapter logs. +- `ACP_DISABLE_TITLE_GENERATION` - set to `1` to skip the adapter's extra model call for session titles. Applies to every session in this adapter process; unset or other values retain automatic generation. Existing titles, prompt-derived fallback titles, and `/rename` remain available. This is an adapter-specific option, not an ACP protocol setting. ### Quick start diff --git a/src/TitleGenerator.ts b/src/TitleGenerator.ts index b606e9d0..113c6839 100644 --- a/src/TitleGenerator.ts +++ b/src/TitleGenerator.ts @@ -46,6 +46,7 @@ export class TitleGenerator { * not turn.items — turn.items contains only agent output). */ onTurnCompleted(userPromptText: string): void { + if (process.env["ACP_DISABLE_TITLE_GENERATION"] === "1") return; if (this.generated) return; const src = this.getSessionTitleSource(); // "explicit": user renamed or session loaded with a name — skip diff --git a/src/__tests__/TitleGenerator.test.ts b/src/__tests__/TitleGenerator.test.ts new file mode 100644 index 00000000..40a02490 --- /dev/null +++ b/src/__tests__/TitleGenerator.test.ts @@ -0,0 +1,53 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { CodexAppServerClient } from "../CodexAppServerClient"; +import { TitleGenerator } from "../TitleGenerator"; + +describe("TitleGenerator", () => { + afterEach(() => vi.unstubAllEnvs()); + + function client() { + return { + threadStart: vi.fn().mockResolvedValue({ thread: { id: "title-thread" } }), + runTurn: vi.fn().mockResolvedValue({ + turn: { items: [{ type: "agentMessage", text: '{"title":"Review project memory"}' }] }, + }), + threadSetName: vi.fn().mockResolvedValue({}), + }; + } + + it("starts no title thread or model request when disabled", async () => { + vi.stubEnv("ACP_DISABLE_TITLE_GENERATION", "1"); + const codex = client(); + const titles = new TitleGenerator( + codex as unknown as CodexAppServerClient, "session", "/workspace", () => "unset", + ); + + titles.onTurnCompleted("Review the project's memory notes"); + titles.onTurnCompleted("Review another note"); + await new Promise(resolve => setImmediate(resolve)); + + expect(codex.threadStart).not.toHaveBeenCalled(); + expect(codex.runTurn).not.toHaveBeenCalled(); + expect(codex.threadSetName).not.toHaveBeenCalled(); + }); + + it.each([undefined, "0"])("still generates a title when the switch is %s", async value => { + vi.stubEnv("ACP_DISABLE_TITLE_GENERATION", value); + const codex = client(); + const titles = new TitleGenerator( + codex as unknown as CodexAppServerClient, "session", "/workspace", () => "unset", + ); + + titles.onTurnCompleted("Review the project's memory notes"); + await vi.waitFor(() => expect(codex.threadSetName).toHaveBeenCalledWith({ + threadId: "session", + name: "Review project memory", + })); + + expect(codex.threadStart).toHaveBeenCalledExactlyOnceWith({ + cwd: "/workspace", + ephemeral: true, + }); + expect(codex.runTurn).toHaveBeenCalledTimes(1); + }); +});