From b83f79cb2f9ea008e7bb88d001e224a8016bdcd1 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 19 Jan 2026 15:36:59 +0100 Subject: [PATCH] feat: allow `output.preventDefault` for `command.before.execute` --- .../src/cli/cmd/tui/component/prompt/index.tsx | 6 ++++-- packages/opencode/src/server/routes/session.ts | 4 ++++ packages/opencode/src/session/prompt.ts | 10 +++++++++- packages/plugin/src/index.ts | 2 +- packages/sdk/js/src/v2/gen/types.gen.ts | 4 ++++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 730da20c265..647f053374f 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -551,8 +551,8 @@ export function Prompt(props: PromptProps) { return sync.data.command.some((x) => x.name === command) }) ) { - let [command, ...args] = inputText.split(" ") - sdk.client.session.command({ + const [command, ...args] = inputText.split(" ") + const result = await sdk.client.session.command({ sessionID, command: command.slice(1), arguments: args.join(" "), @@ -567,6 +567,8 @@ export function Prompt(props: PromptProps) { ...x, })), }) + input.clear() + if (result.response.status === 204) return } else { sdk.client.session .prompt({ diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index a98624dfae2..8a7d2ea55e1 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -782,6 +782,9 @@ export const SessionRoutes = lazy(() => }, }, }, + 204: { + description: "Command execution was prevented by a plugin", + }, ...errors(400, 404), }, }), @@ -796,6 +799,7 @@ export const SessionRoutes = lazy(() => const sessionID = c.req.valid("param").sessionID const body = c.req.valid("json") const msg = await SessionPrompt.command({ ...body, sessionID }) + if (!msg) return c.body(null, 204) return c.json(msg) }, ) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f4793d1a798..2d3e579540f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1702,6 +1702,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the : await lastModel(input.sessionID) : taskModel + let defaultPrevented = false await Plugin.trigger( "command.execute.before", { @@ -1709,9 +1710,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID: input.sessionID, arguments: input.arguments, }, - { parts }, + { + parts: parts as any, + preventDefault() { + defaultPrevented = true + }, + }, ) + if (defaultPrevented) return undefined + const result = (await prompt({ sessionID: input.sessionID, messageID: input.messageID, diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 36a4657d74c..2d5911f8437 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -175,7 +175,7 @@ export interface Hooks { "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise "command.execute.before"?: ( input: { command: string; sessionID: string; arguments: string }, - output: { parts: Part[] }, + output: { parts: Part[]; preventDefault(): void }, ) => Promise "tool.execute.before"?: ( input: { tool: string; sessionID: string; callID: string }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 75540f90724..e117dbe8682 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3504,6 +3504,10 @@ export type SessionCommandResponses = { info: AssistantMessage parts: Array } + /** + * Command execution was prevented by a plugin + */ + 204: void } export type SessionCommandResponse = SessionCommandResponses[keyof SessionCommandResponses]