Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions packages/opencode/src/kilocode/suggestion/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ const log = Log.create({ service: "tool.suggest" })

const Params = Schema.Struct({
suggest: Schema.String.annotate({ description: "Short suggestion text shown to the user" }),
actions: Schema.Array(Suggestion.ActionSchema)
.check(Schema.isMinLength(1), Schema.isMaxLength(2))
.annotate({ description: "Available actions the user can take" }),
label: Schema.String.annotate({ description: "Button label for the action (1-5 words)" }),
prompt: Schema.String.annotate({ description: "Synthetic user prompt to inject when accepted" }),
description: Schema.optional(Schema.String).annotate({ description: "Brief explanation of the action" }),
label2: Schema.optional(Schema.String).annotate({ description: "Button label for a second optional action (1-5 words)" }),
prompt2: Schema.optional(Schema.String).annotate({ description: "Synthetic user prompt for the second action" }),
description2: Schema.optional(Schema.String).annotate({ description: "Brief explanation of the second action" }),
})

type Meta = {
Expand Down Expand Up @@ -71,10 +74,17 @@ export const SuggestTool = Tool.define<typeof Params, Meta, Command.Service | Se
parameters: Params,
execute: (params, ctx) =>
Effect.gen(function* () {
const actions: Array<{ label: string; prompt: string; description?: string }> = [
{ label: params.label, prompt: params.prompt, description: params.description },
]
if (params.label2 && params.prompt2) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The flat schema can't enforce that label2 and prompt2 arrive together.

The previous array schema validated each action against Suggestion.ActionSchema (which requires both label and prompt), so a malformed second action would fail at the schema layer. With the flat fields, if a model emits label2 without prompt2 (or vice versa), this if silently drops the second action and the user only sees one button — the model gets no signal that its intended second action was discarded. Since the whole motivation here is Claude emitting partial/malformed JSON, this is exactly the case to guard against.

Two options: (1) enforce consistency in the schema (e.g. a refine/check requiring label2 and prompt2 together), or (2) at minimum log.warn here when only one of the pair is present so a dropped second action is observable. The tool.txt guidance also doesn't mention that label2 and prompt2 are required together — worth stating explicitly.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

actions.push({ label: params.label2, prompt: params.prompt2, description: params.description2 })
}

const promise = Suggestion.show({
sessionID: ctx.sessionID,
text: params.suggest,
actions: params.actions.map((a) => ({ ...a })),
actions,
blocking: false, // render above an active input; VS Code does the same
tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,
})
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/kilocode/suggestion/tool.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Guidelines:
- Do not suggest review after every edit or partial implementation turn
- Do not repeat a review suggestion when one has already been made in the current session
- Keep the suggestion text concise and actionable
- Provide 1-2 actions maximum
- Provide 1-2 actions maximum using the flat fields: `label`/`prompt`/`description` for the first action, and optionally `label2`/`prompt2`/`description2` for a second action
- Make each action prompt self-contained so it can be injected as a synthetic user message
- If you need a real answer from the user, use the `question` tool instead

Expand Down
27 changes: 18 additions & 9 deletions packages/opencode/test/kilocode/suggestion/tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ describe("tool.suggest", () => {
const result = yield* tool.execute(
{
suggest: "Run review?",
actions: [{ label: "Start", prompt: "/local-review-uncommitted" }],
label: "Start",
prompt: "/local-review-uncommitted",
},
ctx as any,
)
Expand Down Expand Up @@ -112,7 +113,8 @@ describe("tool.suggest", () => {
const result = yield* tool.execute(
{
suggest: "Run review?",
actions: [{ label: "Start review", prompt: "/local-review-uncommitted" }],
label: "Start review",
prompt: "/local-review-uncommitted",
},
ctx as any,
)
Expand Down Expand Up @@ -141,7 +143,8 @@ describe("tool.suggest", () => {
const result = yield* tool.execute(
{
suggest: "Tests might need running",
actions: [{ label: "Run tests", prompt: "Run the test suite and fix any failures" }],
label: "Run tests",
prompt: "Run the test suite and fix any failures",
},
ctx as any,
)
Expand All @@ -165,7 +168,8 @@ describe("tool.suggest", () => {
const result = yield* tool.execute(
{
suggest: "Try this?",
actions: [{ label: "Unknown cmd", prompt: "/nonexistent-command" }],
label: "Unknown cmd",
prompt: "/nonexistent-command",
},
ctx as any,
)
Expand Down Expand Up @@ -193,7 +197,8 @@ describe("tool.suggest", () => {
const result = yield* tool.execute(
{
suggest: "Run review?",
actions: [{ label: "Start review", prompt: "/local-review-uncommitted" }],
label: "Start review",
prompt: "/local-review-uncommitted",
},
ctx as any,
)
Expand All @@ -215,7 +220,8 @@ describe("tool.suggest", () => {
yield* tool.execute(
{
suggest: "Run review?",
actions: [{ label: "Start", prompt: "/local-review-uncommitted" }],
label: "Start",
prompt: "/local-review-uncommitted",
},
ctx as any,
)
Expand Down Expand Up @@ -247,7 +253,8 @@ describe("tool.suggest", () => {
.execute(
{
suggest: "Run review?",
actions: [{ label: "Start", prompt: "do it" }],
label: "Start",
prompt: "do it",
},
ctx as any,
)
Expand Down Expand Up @@ -277,7 +284,8 @@ describe("tool.suggest", () => {
yield* tool.execute(
{
suggest: "Go?",
actions: [{ label: "Go", prompt: "go" }],
label: "Go",
prompt: "go",
},
ctx as any,
)
Expand All @@ -298,7 +306,8 @@ describe("tool.suggest", () => {
yield* tool.execute(
{
suggest: "Go?",
actions: [{ label: "Go", prompt: "go" }],
label: "Go",
prompt: "go",
},
ctx as any,
)
Expand Down
Loading