diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 48ee884cb..d781f0df3 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -30,3 +30,4 @@ export { createTaskResumeInfoHook } from "./task-resume-info"; export { createStartWorkHook } from "./start-work"; export { createAtlasHook } from "./atlas"; export { createDelegateTaskRetryHook } from "./delegate-task-retry"; +export { createQuestionLabelTruncatorHook } from "./question-label-truncator"; diff --git a/src/hooks/question-label-truncator/index.ts b/src/hooks/question-label-truncator/index.ts new file mode 100644 index 000000000..cc82d7fc6 --- /dev/null +++ b/src/hooks/question-label-truncator/index.ts @@ -0,0 +1,63 @@ +import type { Plugin } from "@opencode-ai/plugin"; + +const MAX_LABEL_LENGTH = 30; + +interface QuestionOption { + label: string; + description?: string; +} + +interface Question { + question: string; + header?: string; + options: QuestionOption[]; + multiSelect?: boolean; +} + +interface AskUserQuestionArgs { + questions: Question[]; +} + +function truncateLabel(label: string, maxLength: number = MAX_LABEL_LENGTH): string { + if (label.length <= maxLength) { + return label; + } + return label.substring(0, maxLength - 3) + "..."; +} + +function truncateQuestionLabels(args: AskUserQuestionArgs): AskUserQuestionArgs { + if (!args.questions || !Array.isArray(args.questions)) { + return args; + } + + return { + ...args, + questions: args.questions.map((question) => ({ + ...question, + options: question.options?.map((option) => ({ + ...option, + label: truncateLabel(option.label), + })) ?? [], + })), + }; +} + +export function createQuestionLabelTruncatorHook(): Pick< + Plugin, + "tool.execute.before" +> { + return { + "tool.execute.before": async (input, output) => { + const toolName = input.tool?.toLowerCase(); + + if (toolName === "askuserquestion" || toolName === "ask_user_question") { + const args = output.args as AskUserQuestionArgs | undefined; + + if (args?.questions) { + const truncatedArgs = truncateQuestionLabels(args); + Object.assign(output.args as object, truncatedArgs); + } + } + }, + }; +} diff --git a/src/index.ts b/src/index.ts index d38a76ac3..d3375c421 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { createStartWorkHook, createAtlasHook, createPrometheusMdOnlyHook, + createQuestionLabelTruncatorHook, } from "./hooks"; import { contextCollector, @@ -203,6 +204,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { ? createPrometheusMdOnlyHook(ctx) : null; + const questionLabelTruncator = createQuestionLabelTruncatorHook(); + const taskResumeInfo = createTaskResumeInfoHook(); const backgroundManager = new BackgroundManager(ctx, pluginConfig.background_task); @@ -484,6 +487,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { }, "tool.execute.before": async (input, output) => { + await questionLabelTruncator["tool.execute.before"]?.(input, output); await claudeCodeHooks["tool.execute.before"](input, output); await nonInteractiveEnv?.["tool.execute.before"](input, output); await commentChecker?.["tool.execute.before"](input, output);