From fc5bf9afd79f471cde7f985864a481f373da09ec Mon Sep 17 00:00:00 2001 From: ImmuneFOMO Date: Sat, 14 Feb 2026 06:18:12 +0000 Subject: [PATCH 1/3] feat(acp): add opt-in flag for question tool --- packages/opencode/src/acp/README.md | 10 +++++ packages/opencode/src/flag/flag.ts | 12 ++++++ packages/opencode/src/tool/registry.ts | 5 ++- packages/opencode/test/tool/registry.test.ts | 42 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/acp/README.md b/packages/opencode/src/acp/README.md index d998cb22da8d..2a4e42998d8b 100644 --- a/packages/opencode/src/acp/README.md +++ b/packages/opencode/src/acp/README.md @@ -44,6 +44,16 @@ opencode acp opencode acp --cwd /path/to/project ``` +### Question Tool Opt-In + +ACP excludes `QuestionTool` by default. + +```bash +OPENCODE_ENABLE_ACP_QUESTION_TOOL=1 opencode acp +``` + +Enable this only for ACP clients that support interactive question prompts. + ### Programmatic ```typescript diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index dfcb88bc51a5..220e03ddcecb 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -28,6 +28,7 @@ export namespace Flag { export declare const OPENCODE_DISABLE_PROJECT_CONFIG: boolean export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"] export declare const OPENCODE_CLIENT: string + export declare const OPENCODE_ENABLE_ACP_QUESTION_TOOL: boolean export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"] export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"] @@ -94,3 +95,14 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", { enumerable: true, configurable: false, }) + +// Dynamic getter for OPENCODE_ENABLE_ACP_QUESTION_TOOL +// This must be evaluated at access time, not module load time, +// because ACP integrations may set this env var at runtime +Object.defineProperty(Flag, "OPENCODE_ENABLE_ACP_QUESTION_TOOL", { + get() { + return truthy("OPENCODE_ENABLE_ACP_QUESTION_TOOL") + }, + enumerable: true, + configurable: false, +}) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 9a06cb59937b..250d2e82d7b2 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -94,10 +94,13 @@ export namespace ToolRegistry { async function all(): Promise { const custom = await state().then((x) => x.custom) const config = await Config.get() + const question = + ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || + (Flag.OPENCODE_CLIENT === "acp" && Flag.OPENCODE_ENABLE_ACP_QUESTION_TOOL) return [ InvalidTool, - ...(["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) ? [QuestionTool] : []), + ...(question ? [QuestionTool] : []), BashTool, ReadTool, GlobTool, diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 706a9e12caf9..1d24012fe2f1 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -5,6 +5,29 @@ import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { ToolRegistry } from "../../src/tool/registry" +async function ids(client: string, flag?: string) { + const originalClient = process.env["OPENCODE_CLIENT"] + const originalFlag = process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] + + try { + process.env["OPENCODE_CLIENT"] = client + if (flag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] + if (flag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = flag + + await using tmp = await tmpdir() + return await Instance.provide({ + directory: tmp.path, + fn: async () => ToolRegistry.ids(), + }) + } finally { + if (originalClient === undefined) delete process.env["OPENCODE_CLIENT"] + if (originalClient !== undefined) process.env["OPENCODE_CLIENT"] = originalClient + + if (originalFlag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] + if (originalFlag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = originalFlag + } +} + describe("tool.registry", () => { test("loads tools from .opencode/tool (singular)", async () => { await using tmp = await tmpdir({ @@ -119,4 +142,23 @@ describe("tool.registry", () => { }, }) }) + + test("excludes question tool for acp when flag is unset", async () => { + expect(await ids("acp")).not.toContain("question") + }) + + test("excludes question tool for acp when flag is 0", async () => { + expect(await ids("acp", "0")).not.toContain("question") + }) + + test("includes question tool for acp when flag is enabled", async () => { + expect(await ids("acp", "1")).toContain("question") + expect(await ids("acp", "true")).toContain("question") + }) + + test("keeps question tool for app, cli, and desktop", async () => { + expect(await ids("app", "0")).toContain("question") + expect(await ids("cli", "0")).toContain("question") + expect(await ids("desktop", "0")).toContain("question") + }) }) From fd3f58c6eb62198d67d513c061c3684bc7006d69 Mon Sep 17 00:00:00 2001 From: ImmuneFOMO Date: Sun, 15 Feb 2026 00:07:05 +0000 Subject: [PATCH 2/3] feat(acp): rename question flag to experimental --- packages/opencode/src/acp/README.md | 4 ++-- packages/opencode/src/flag/flag.ts | 10 +++++----- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/test/tool/registry.test.ts | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/acp/README.md b/packages/opencode/src/acp/README.md index 2a4e42998d8b..ef7dc14b6146 100644 --- a/packages/opencode/src/acp/README.md +++ b/packages/opencode/src/acp/README.md @@ -44,12 +44,12 @@ opencode acp opencode acp --cwd /path/to/project ``` -### Question Tool Opt-In +### Experimental Question Tool Opt-In ACP excludes `QuestionTool` by default. ```bash -OPENCODE_ENABLE_ACP_QUESTION_TOOL=1 opencode acp +OPENCODE_EXPERIMENTAL_QUESTION_TOOL=1 opencode acp ``` Enable this only for ACP clients that support interactive question prompts. diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 220e03ddcecb..c4dcd985dd9f 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -28,7 +28,6 @@ export namespace Flag { export declare const OPENCODE_DISABLE_PROJECT_CONFIG: boolean export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"] export declare const OPENCODE_CLIENT: string - export declare const OPENCODE_ENABLE_ACP_QUESTION_TOOL: boolean export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"] export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"] @@ -52,6 +51,7 @@ export namespace Flag { export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN") + export declare const OPENCODE_EXPERIMENTAL_QUESTION_TOOL: boolean export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] @@ -96,12 +96,12 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", { configurable: false, }) -// Dynamic getter for OPENCODE_ENABLE_ACP_QUESTION_TOOL +// Dynamic getter for OPENCODE_EXPERIMENTAL_QUESTION_TOOL // This must be evaluated at access time, not module load time, -// because ACP integrations may set this env var at runtime -Object.defineProperty(Flag, "OPENCODE_ENABLE_ACP_QUESTION_TOOL", { +// because external tooling may set this env var at runtime +Object.defineProperty(Flag, "OPENCODE_EXPERIMENTAL_QUESTION_TOOL", { get() { - return truthy("OPENCODE_ENABLE_ACP_QUESTION_TOOL") + return truthy("OPENCODE_EXPERIMENTAL_QUESTION_TOOL") }, enumerable: true, configurable: false, diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 250d2e82d7b2..0f87c83ee85f 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -96,7 +96,7 @@ export namespace ToolRegistry { const config = await Config.get() const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || - (Flag.OPENCODE_CLIENT === "acp" && Flag.OPENCODE_ENABLE_ACP_QUESTION_TOOL) + (Flag.OPENCODE_CLIENT === "acp" && Flag.OPENCODE_EXPERIMENTAL_QUESTION_TOOL) return [ InvalidTool, diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 1d24012fe2f1..78b0b485e3ee 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -7,12 +7,12 @@ import { ToolRegistry } from "../../src/tool/registry" async function ids(client: string, flag?: string) { const originalClient = process.env["OPENCODE_CLIENT"] - const originalFlag = process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] + const originalFlag = process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] try { process.env["OPENCODE_CLIENT"] = client - if (flag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] - if (flag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = flag + if (flag === undefined) delete process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] + if (flag !== undefined) process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] = flag await using tmp = await tmpdir() return await Instance.provide({ @@ -23,8 +23,8 @@ async function ids(client: string, flag?: string) { if (originalClient === undefined) delete process.env["OPENCODE_CLIENT"] if (originalClient !== undefined) process.env["OPENCODE_CLIENT"] = originalClient - if (originalFlag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] - if (originalFlag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = originalFlag + if (originalFlag === undefined) delete process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] + if (originalFlag !== undefined) process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] = originalFlag } } @@ -143,15 +143,15 @@ describe("tool.registry", () => { }) }) - test("excludes question tool for acp when flag is unset", async () => { + test("excludes question tool for acp when experimental flag is unset", async () => { expect(await ids("acp")).not.toContain("question") }) - test("excludes question tool for acp when flag is 0", async () => { + test("excludes question tool for acp when experimental flag is 0", async () => { expect(await ids("acp", "0")).not.toContain("question") }) - test("includes question tool for acp when flag is enabled", async () => { + test("includes question tool for acp when experimental flag is enabled", async () => { expect(await ids("acp", "1")).toContain("question") expect(await ids("acp", "true")).toContain("question") }) From 9042851d1d025c0fb5f1163bca2cb08adc958bf1 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Mon, 16 Feb 2026 14:14:12 -0600 Subject: [PATCH 3/3] tweak --- .opencode/agent/translator.md | 1 + packages/opencode/src/acp/README.md | 4 +- packages/opencode/src/flag/flag.ts | 13 +----- packages/opencode/src/tool/registry.ts | 4 +- packages/opencode/test/tool/registry.test.ts | 42 -------------------- 5 files changed, 5 insertions(+), 59 deletions(-) diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md index dec6fa6c4fc3..a2f2784e91fc 100644 --- a/.opencode/agent/translator.md +++ b/.opencode/agent/translator.md @@ -598,6 +598,7 @@ OPENCODE_EXPERIMENTAL_MARKDOWN OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX OPENCODE_EXPERIMENTAL_OXFMT OPENCODE_EXPERIMENTAL_PLAN_MODE +OPENCODE_ENABLE_QUESTION_TOOL OPENCODE_FAKE_VCS OPENCODE_GIT_BASH_PATH OPENCODE_MODEL diff --git a/packages/opencode/src/acp/README.md b/packages/opencode/src/acp/README.md index ef7dc14b6146..aab33259bb18 100644 --- a/packages/opencode/src/acp/README.md +++ b/packages/opencode/src/acp/README.md @@ -44,12 +44,12 @@ opencode acp opencode acp --cwd /path/to/project ``` -### Experimental Question Tool Opt-In +### Question Tool Opt-In ACP excludes `QuestionTool` by default. ```bash -OPENCODE_EXPERIMENTAL_QUESTION_TOOL=1 opencode acp +OPENCODE_ENABLE_QUESTION_TOOL=1 opencode acp ``` Enable this only for ACP clients that support interactive question prompts. diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index c4dcd985dd9f..0049d716d095 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -30,6 +30,7 @@ export namespace Flag { export declare const OPENCODE_CLIENT: string export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"] export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"] + export const OPENCODE_ENABLE_QUESTION_TOOL = truthy("OPENCODE_ENABLE_QUESTION_TOOL") // Experimental export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL") @@ -51,7 +52,6 @@ export namespace Flag { export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN") - export declare const OPENCODE_EXPERIMENTAL_QUESTION_TOOL: boolean export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] @@ -95,14 +95,3 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", { enumerable: true, configurable: false, }) - -// Dynamic getter for OPENCODE_EXPERIMENTAL_QUESTION_TOOL -// This must be evaluated at access time, not module load time, -// because external tooling may set this env var at runtime -Object.defineProperty(Flag, "OPENCODE_EXPERIMENTAL_QUESTION_TOOL", { - get() { - return truthy("OPENCODE_EXPERIMENTAL_QUESTION_TOOL") - }, - enumerable: true, - configurable: false, -}) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0f87c83ee85f..3ff9cce8990f 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -94,9 +94,7 @@ export namespace ToolRegistry { async function all(): Promise { const custom = await state().then((x) => x.custom) const config = await Config.get() - const question = - ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || - (Flag.OPENCODE_CLIENT === "acp" && Flag.OPENCODE_EXPERIMENTAL_QUESTION_TOOL) + const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL return [ InvalidTool, diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 78b0b485e3ee..706a9e12caf9 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -5,29 +5,6 @@ import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { ToolRegistry } from "../../src/tool/registry" -async function ids(client: string, flag?: string) { - const originalClient = process.env["OPENCODE_CLIENT"] - const originalFlag = process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] - - try { - process.env["OPENCODE_CLIENT"] = client - if (flag === undefined) delete process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] - if (flag !== undefined) process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] = flag - - await using tmp = await tmpdir() - return await Instance.provide({ - directory: tmp.path, - fn: async () => ToolRegistry.ids(), - }) - } finally { - if (originalClient === undefined) delete process.env["OPENCODE_CLIENT"] - if (originalClient !== undefined) process.env["OPENCODE_CLIENT"] = originalClient - - if (originalFlag === undefined) delete process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] - if (originalFlag !== undefined) process.env["OPENCODE_EXPERIMENTAL_QUESTION_TOOL"] = originalFlag - } -} - describe("tool.registry", () => { test("loads tools from .opencode/tool (singular)", async () => { await using tmp = await tmpdir({ @@ -142,23 +119,4 @@ describe("tool.registry", () => { }, }) }) - - test("excludes question tool for acp when experimental flag is unset", async () => { - expect(await ids("acp")).not.toContain("question") - }) - - test("excludes question tool for acp when experimental flag is 0", async () => { - expect(await ids("acp", "0")).not.toContain("question") - }) - - test("includes question tool for acp when experimental flag is enabled", async () => { - expect(await ids("acp", "1")).toContain("question") - expect(await ids("acp", "true")).toContain("question") - }) - - test("keeps question tool for app, cli, and desktop", async () => { - expect(await ids("app", "0")).toContain("question") - expect(await ids("cli", "0")).toContain("question") - expect(await ids("desktop", "0")).toContain("question") - }) })