From aaab05a6b6d1b2a3a11e6c09c5b9f5c3c1aedf08 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 11:27:24 +0200 Subject: [PATCH 01/33] feat(cli): add allowEverything function and pending accessor to PermissionNext Add allowEverything() to enable/disable a wildcard allow-all rule in the in-memory approved list and resolve pending permission requests. When enabling with a sessionID, only that session's pending requests are drained. When enabling globally (no sessionID), a wildcard rule is added to s.approved and all pending requests are resolved. Add pending() accessor to look up a pending request's info by ID without exposing the private state singleton. --- packages/opencode/src/permission/next.ts | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 5534572da26..ddefe350694 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -334,6 +334,53 @@ export namespace PermissionNext { }, ) + // kilocode_change start + export const allowEverything = fn( + z.object({ + enable: z.boolean(), + requestID: Identifier.schema("permission").optional(), + sessionID: Identifier.schema("session").optional(), + }), + async (input) => { + const s = await state() + + if (!input.enable) { + const idx = s.approved.findLastIndex((r) => r.permission === "*" && r.pattern === "*" && r.action === "allow") + if (idx >= 0) s.approved.splice(idx, 1) + return + } + + if (!input.sessionID) { + s.approved.push({ permission: "*", pattern: "*", action: "allow" }) + } + + if (input.requestID) { + const existing = s.pending[input.requestID] + if (existing) { + delete s.pending[input.requestID] + Bus.publish(Event.Replied, { + sessionID: existing.info.sessionID, + requestID: existing.info.id, + reply: "once", + }) + existing.resolve() + } + } + + for (const [id, entry] of Object.entries(s.pending)) { + if (input.sessionID && entry.info.sessionID !== input.sessionID) continue + delete s.pending[id] + Bus.publish(Event.Replied, { + sessionID: entry.info.sessionID, + requestID: entry.info.id, + reply: "once", + }) + entry.resolve() + } + }, + ) + // kilocode_change end + export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule { const merged = merge(...rulesets) log.info("evaluate", { permission, pattern, ruleset: merged }) @@ -384,4 +431,11 @@ export namespace PermissionNext { const s = await state() return Object.values(s.pending).map((x) => x.info) } + + // kilocode_change start + export async function pending(id: string): Promise { + const s = await state() + return s.pending[id]?.info + } + // kilocode_change end } From 47d008c5d38db8cd1775c4055937e66c5779071d Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 11:27:34 +0200 Subject: [PATCH 02/33] feat(cli): add POST /allow-everything server route Add a new route that enables or disables allowing all permissions. The route handler orchestrates persistence: session-scoped requests update the session's permission ruleset via Session.setPermission, while global requests update the global config via Config.updateGlobal. Both delegate to PermissionNext.allowEverything for in-memory state management. This avoids a circular dependency between next.ts and session/index.ts by keeping Session imports in the route handler only. --- .../opencode/src/server/routes/permission.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/opencode/src/server/routes/permission.ts b/packages/opencode/src/server/routes/permission.ts index 701de5a098e..6bd9e18af87 100644 --- a/packages/opencode/src/server/routes/permission.ts +++ b/packages/opencode/src/server/routes/permission.ts @@ -2,6 +2,8 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" import { PermissionNext } from "@/permission/next" +import { Session } from "@/session" // kilocode_change +import { Config } from "@/config/config" // kilocode_change import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -93,6 +95,64 @@ export const PermissionRoutes = lazy(() => }, ) // kilocode_change end + // kilocode_change start + .post( + "/allow-everything", + describeRoute({ + summary: "Allow everything", + description: "Enable or disable allowing all permissions without prompts.", + operationId: "permission.allowEverything", + responses: { + 200: { + description: "Success", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(400, 404), + }, + }), + validator( + "json", + z.object({ + enable: z.boolean(), + requestID: z.string().optional(), + sessionID: z.string().optional(), + }), + ), + async (c) => { + const json = c.req.valid("json") + const rules: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "allow" }] + + if (!json.enable) { + await Config.updateGlobal({ permission: { "*": { "*": null } } }, { dispose: false }) + await PermissionNext.allowEverything({ enable: false }) + return c.json(true) + } + + if (json.sessionID) { + const session = await Session.get(json.sessionID) + const existing = session.permission ?? [] + await Session.setPermission({ + sessionID: json.sessionID, + permission: [...existing, ...rules], + }) + } else { + await Config.updateGlobal({ permission: PermissionNext.toConfig(rules) }, { dispose: false }) + } + + await PermissionNext.allowEverything({ + enable: true, + requestID: json.requestID, + sessionID: json.sessionID, + }) + + return c.json(true) + }, + ) + // kilocode_change end .get( "/", describeRoute({ From 338a7b96178bde35249b82846ae634b99d99164d Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 11:27:40 +0200 Subject: [PATCH 03/33] feat(cli): add 'Allow everything' stage to TUI permission prompt Add an 'everything' stage to the permission prompt that lets users choose between session-scoped and global allow-everything. The main permission prompt now shows four options: Allow once, Allow always, Allow everything, and Reject. Selecting 'Allow everything' transitions to a sub-prompt with 'This session', 'All sessions', and 'Cancel'. For 'All sessions', the KV flag allow_everything_global is set so the ctrl+p toggle reflects the current state. --- .../cli/cmd/tui/routes/session/permission.tsx | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index c2cd4d17d9c..d3baeb69ee3 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -6,6 +6,7 @@ import { useKeybind } from "../../context/keybind" import { useTheme, selectedForeground } from "../../context/theme" import type { PermissionRequest } from "@kilocode/sdk/v2" import { useSDK } from "../../context/sdk" +import { useKV } from "../../context/kv" // kilocode_change import { SplitBorder } from "../../component/border" import { useSync } from "../../context/sync" import { useTextareaKeybindings } from "../../component/textarea-keybindings" @@ -17,7 +18,7 @@ import { Global } from "@/global" import { useDialog } from "../../ui/dialog" import { useTuiConfig } from "../../context/tui-config" -type PermissionStage = "permission" | "always" | "reject" +type PermissionStage = "permission" | "always" | "everything" | "reject" // kilocode_change function normalizePath(input?: string) { if (!input) return "" @@ -129,6 +130,7 @@ function TextBody(props: { title: string; description?: string; icon?: string }) export function PermissionPrompt(props: { request: PermissionRequest }) { const sdk = useSDK() const sync = useSync() + const kv = useKV() // kilocode_change const [store, setStore] = createStore({ stage: "permission" as PermissionStage, }) @@ -190,6 +192,33 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { }} /> + {/* kilocode_change start */} + + } + options={{ session: "This session", all: "All sessions", cancel: "Cancel" }} + escapeKey="cancel" + onSelect={(option) => { + setStore("stage", "permission") + if (option === "cancel") return + if (option === "session") { + sdk.client.permission.allowEverything({ + enable: true, + requestID: props.request.id, + sessionID: props.request.sessionID, + }) + return + } + sdk.client.permission.allowEverything({ + enable: true, + requestID: props.request.id, + }) + kv.set("allow_everything_global", true) + }} + /> + + {/* kilocode_change end */} { @@ -433,7 +462,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { title="Permission required" header={header()} body={current.body} - options={{ once: "Allow once", always: "Allow always", reject: "Reject" }} + options={{ once: "Allow once", always: "Allow always", everything: "Allow everything", reject: "Reject" }} escapeKey="reject" fullscreen onSelect={(option) => { @@ -441,6 +470,12 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { setStore("stage", "always") return } + // kilocode_change start + if (option === "everything") { + setStore("stage", "everything") + return + } + // kilocode_change end if (option === "reject") { if (session()?.parentID) { setStore("stage", "reject") From 54ecd4ade1b168bc483d044c98c71f9503158492 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 11:27:47 +0200 Subject: [PATCH 04/33] feat(cli): add allow everything toggle to command palette Add 'Enable/Disable allow everything' command to the System category in the ctrl+p command palette. Uses the KV flag allow_everything_global to track and display the current state. Toggling calls the SDK endpoint and updates the KV flag. --- packages/opencode/src/cli/cmd/tui/app.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 2fdc41b3934..efcc50b6f88 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -702,6 +702,19 @@ function App() { dialog.clear() }, }, + // kilocode_change start + { + title: kv.get("allow_everything_global") ? "Disable allow everything" : "Enable allow everything", + value: "permission.allow_everything", + category: "System", + onSelect: (dialog) => { + const enabled = kv.get("allow_everything_global") + sdk.client.permission.allowEverything({ enable: !enabled }) + kv.set("allow_everything_global", !enabled) + dialog.clear() + }, + }, + // kilocode_change end ]) // kilocode_change start - Initialize TUI dependencies for kilo-gateway From 6ee80e1d9779faaf3730f4194620dad01fc28100 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 11:27:53 +0200 Subject: [PATCH 05/33] chore(sdk): regenerate SDK for allow-everything endpoint Auto-generated via ./script/generate.ts after adding the permission.allowEverything route. --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 47 + packages/sdk/js/src/v2/gen/types.gen.ts | 37 + packages/sdk/openapi.json | 1969 ++++++----------------- 3 files changed, 543 insertions(+), 1510 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 640cc66fb0d..b64dca164d3 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -93,6 +93,8 @@ import type { PartUpdateErrors, PartUpdateResponses, PathGetResponses, + PermissionAllowEverythingErrors, + PermissionAllowEverythingResponses, PermissionListResponses, PermissionReplyErrors, PermissionReplyResponses, @@ -2579,6 +2581,51 @@ export class Permission extends HeyApiClient { }) } + /** + * Allow everything + * + * Enable or disable allowing all permissions without prompts. + */ + public allowEverything( + parameters?: { + directory?: string + workspace?: string + enable?: boolean + requestID?: string + sessionID?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "enable" }, + { in: "body", key: "requestID" }, + { in: "body", key: "sessionID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + PermissionAllowEverythingResponses, + PermissionAllowEverythingErrors, + ThrowOnError + >({ + url: "/permission/allow-everything", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + /** * List pending permissions * diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index af5ae1d6b6d..a7080537a34 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4039,6 +4039,43 @@ export type PermissionSaveAlwaysRulesResponses = { export type PermissionSaveAlwaysRulesResponse = PermissionSaveAlwaysRulesResponses[keyof PermissionSaveAlwaysRulesResponses] +export type PermissionAllowEverythingData = { + body?: { + enable: boolean + requestID?: string + sessionID?: string + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/permission/allow-everything" +} + +export type PermissionAllowEverythingErrors = { + /** + * Bad request + */ + 400: BadRequestError + /** + * Not found + */ + 404: NotFoundError +} + +export type PermissionAllowEverythingError = PermissionAllowEverythingErrors[keyof PermissionAllowEverythingErrors] + +export type PermissionAllowEverythingResponses = { + /** + * Success + */ + 200: boolean +} + +export type PermissionAllowEverythingResponse = + PermissionAllowEverythingResponses[keyof PermissionAllowEverythingResponses] + export type PermissionListData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 8393d52605d..e77b3440917 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -27,10 +27,7 @@ "type": "string" } }, - "required": [ - "healthy", - "version" - ] + "required": ["healthy", "version"] } } } @@ -745,10 +742,7 @@ "type": "number" } }, - "required": [ - "rows", - "cols" - ] + "required": ["rows", "cols"] } } } @@ -1023,10 +1017,7 @@ } } }, - "required": [ - "providers", - "default" - ] + "required": ["providers", "default"] } } } @@ -1233,11 +1224,7 @@ ] } }, - "required": [ - "type", - "branch", - "extra" - ] + "required": ["type", "branch", "extra"] } } } @@ -2148,9 +2135,7 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific Kilo session.", - "tags": [ - "Session" - ], + "tags": ["Session"], "responses": { "200": { "description": "Get session", @@ -2377,9 +2362,7 @@ } ], "summary": "Get session children", - "tags": [ - "Session" - ], + "tags": ["Session"], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -2576,11 +2559,7 @@ "pattern": "^msg.*" } }, - "required": [ - "modelID", - "providerID", - "messageID" - ] + "required": ["modelID", "providerID", "messageID"] } } } @@ -3004,10 +2983,7 @@ "type": "boolean" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] } } } @@ -3077,10 +3053,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3161,10 +3134,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3210,10 +3180,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3283,9 +3250,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3355,10 +3320,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3725,10 +3687,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3798,9 +3757,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3861,10 +3818,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3942,20 +3896,13 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] } ] } } }, - "required": [ - "arguments", - "command" - ] + "required": ["arguments", "command"] } } } @@ -4049,19 +3996,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "agent", - "command" - ] + "required": ["agent", "command"] } } } @@ -4150,9 +4091,7 @@ "pattern": "^prt.*" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } } } @@ -4312,16 +4251,10 @@ "properties": { "response": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "response" - ] + "required": ["response"] } } } @@ -4458,19 +4391,13 @@ "properties": { "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] }, "message": { "type": "string" } }, - "required": [ - "reply" - ] + "required": ["reply"] } } } @@ -4575,6 +4502,88 @@ ] } }, + "/permission/allow-everything": { + "post": { + "operationId": "permission.allowEverything", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Allow everything", + "description": "Enable or disable allowing all permissions without prompts.", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "requestID": { + "type": "string" + }, + "sessionID": { + "type": "string" + } + }, + "required": ["enable"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.permission.allowEverything({\n ...\n})" + } + ] + } + }, "/permission": { "get": { "operationId": "permission.list", @@ -4738,9 +4747,7 @@ } } }, - "required": [ - "answers" - ] + "required": ["answers"] } } } @@ -4917,15 +4924,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -4961,16 +4963,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -4985,10 +4981,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -4997,61 +4990,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, + "ai_sdk_provider": { + "type": "string", + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] + }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -5108,12 +5079,7 @@ } } }, - "required": [ - "name", - "env", - "id", - "models" - ] + "required": ["name", "env", "id", "models"] } }, "default": { @@ -5132,11 +5098,7 @@ } } }, - "required": [ - "all", - "default", - "connected" - ] + "required": ["all", "default", "connected"] } } } @@ -5263,9 +5225,7 @@ "type": "number" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5345,9 +5305,7 @@ "type": "string" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5422,9 +5380,7 @@ "additionalProperties": {} } }, - "required": [ - "event" - ] + "required": ["event"] } } } @@ -5620,9 +5576,7 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -5660,9 +5614,7 @@ "type": "string" } }, - "required": [ - "path" - ] + "required": ["path"] } } } @@ -5708,9 +5660,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5738,9 +5688,7 @@ "minLength": 1 } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5806,9 +5754,7 @@ "type": "string" } }, - "required": [ - "location" - ] + "required": ["location"] } } } @@ -5874,9 +5820,7 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } } } @@ -5942,17 +5886,11 @@ "type": "string" } }, - "required": [ - "id", - "name", - "role" - ] + "required": ["id", "name", "role"] } } }, - "required": [ - "email" - ] + "required": ["email"] }, "balance": { "anyOf": [ @@ -5963,9 +5901,7 @@ "type": "number" } }, - "required": [ - "balance" - ] + "required": ["balance"] }, { "type": "null" @@ -5983,11 +5919,7 @@ ] } }, - "required": [ - "profile", - "balance", - "currentOrgId" - ] + "required": ["profile", "balance", "currentOrgId"] } } } @@ -6071,9 +6003,7 @@ ] } }, - "required": [ - "organizationId" - ] + "required": ["organizationId"] } } } @@ -6158,7 +6088,32 @@ }, "groups": { "type": "array", - "items": {} + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "fileRegex": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + ] + } + ] + } } } } @@ -6176,9 +6131,7 @@ } } }, - "required": [ - "modes" - ] + "required": ["modes"] } } } @@ -6289,10 +6242,7 @@ "type": "number" } }, - "required": [ - "prefix", - "suffix" - ] + "required": ["prefix", "suffix"] } } } @@ -6355,10 +6305,7 @@ "type": "string" } }, - "required": [ - "actionText", - "actionURL" - ] + "required": ["actionText", "actionURL"] }, "showIn": { "type": "array", @@ -6370,11 +6317,7 @@ "type": "string" } }, - "required": [ - "id", - "title", - "message" - ] + "required": ["id", "title", "message"] } } } @@ -6517,9 +6460,7 @@ "type": "string" } }, - "required": [ - "sessionId" - ] + "required": ["sessionId"] } } } @@ -6610,13 +6551,7 @@ "type": "number" } }, - "required": [ - "session_id", - "title", - "created_at", - "updated_at", - "version" - ] + "required": ["session_id", "title", "created_at", "updated_at", "version"] } }, "nextCursor": { @@ -6630,10 +6565,7 @@ ] } }, - "required": [ - "cliSessions", - "nextCursor" - ] + "required": ["cliSessions", "nextCursor"] } } } @@ -6703,9 +6635,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "lines": { "type": "object", @@ -6714,9 +6644,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "line_number": { "type": "number" @@ -6736,9 +6664,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "start": { "type": "number" @@ -6747,21 +6673,11 @@ "type": "number" } }, - "required": [ - "match", - "start", - "end" - ] + "required": ["match", "start", "end"] } } }, - "required": [ - "path", - "lines", - "line_number", - "absolute_offset", - "submatches" - ] + "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] } } } @@ -6807,10 +6723,7 @@ "name": "dirs", "schema": { "type": "string", - "enum": [ - "true", - "false" - ] + "enum": ["true", "false"] } }, { @@ -6818,10 +6731,7 @@ "name": "type", "schema": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] } }, { @@ -7170,10 +7080,7 @@ ] } }, - "required": [ - "name", - "config" - ] + "required": ["name", "config"] } } } @@ -7228,9 +7135,7 @@ "type": "string" } }, - "required": [ - "authorizationUrl" - ] + "required": ["authorizationUrl"] } } } @@ -7304,9 +7209,7 @@ "const": true } }, - "required": [ - "success" - ] + "required": ["success"] } } } @@ -7402,9 +7305,7 @@ "type": "string" } }, - "required": [ - "code" - ] + "required": ["code"] } } } @@ -7635,9 +7536,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -7949,9 +7848,7 @@ "type": "string" } }, - "required": [ - "command" - ] + "required": ["command"] } } } @@ -8011,12 +7908,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -8024,10 +7916,7 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } } } @@ -8178,9 +8067,7 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } } } @@ -8227,10 +8114,7 @@ }, "body": {} }, - "required": [ - "path", - "body" - ] + "required": ["path", "body"] } } } @@ -8515,12 +8399,7 @@ "level": { "description": "Log level", "type": "string", - "enum": [ - "debug", - "info", - "error", - "warn" - ] + "enum": ["debug", "info", "error", "warn"] }, "message": { "description": "Log message", @@ -8535,11 +8414,7 @@ "additionalProperties": {} } }, - "required": [ - "service", - "level", - "message" - ] + "required": ["service", "level", "message"] } } } @@ -8640,12 +8515,7 @@ "type": "string" } }, - "required": [ - "name", - "description", - "location", - "content" - ] + "required": ["name", "description", "location", "content"] } } } @@ -8806,15 +8676,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.installation.update-available": { "type": "object", @@ -8830,15 +8695,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Project": { "type": "object", @@ -8892,10 +8752,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "sandboxes": { "type": "array", @@ -8904,12 +8761,7 @@ } } }, - "required": [ - "id", - "worktree", - "time", - "sandboxes" - ] + "required": ["id", "worktree", "time", "sandboxes"] }, "Event.project.updated": { "type": "object", @@ -8922,10 +8774,7 @@ "$ref": "#/components/schemas/Project" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.instance.disposed": { "type": "object", @@ -8941,15 +8790,10 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.connected": { "type": "object", @@ -8963,10 +8807,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.disposed": { "type": "object", @@ -8980,10 +8821,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.config.updated": { "type": "object", @@ -8997,10 +8835,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -9019,16 +8854,10 @@ "type": "string" } }, - "required": [ - "serverID", - "path" - ] + "required": ["serverID", "path"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.updated": { "type": "object", @@ -9042,10 +8871,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.edited": { "type": "object", @@ -9061,15 +8887,10 @@ "type": "string" } }, - "required": [ - "file" - ] + "required": ["file"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "OutputFormatText": { "type": "object", @@ -9079,9 +8900,7 @@ "const": "text" } }, - "required": [ - "type" - ] + "required": ["type"] }, "JSONSchema": { "type": "object", @@ -9107,10 +8926,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "schema" - ] + "required": ["type", "schema"] }, "OutputFormat": { "anyOf": [ @@ -9142,20 +8958,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "before", - "after", - "additions", - "deletions" - ] + "required": ["file", "before", "after", "additions", "deletions"] }, "UserMessage": { "type": "object", @@ -9177,9 +8983,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "format": { "$ref": "#/components/schemas/OutputFormat" @@ -9200,9 +9004,7 @@ } } }, - "required": [ - "diffs" - ] + "required": ["diffs"] }, "agent": { "type": "string" @@ -9217,10 +9019,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "system": { "type": "string" @@ -9261,14 +9060,7 @@ } } }, - "required": [ - "id", - "sessionID", - "role", - "time", - "agent", - "model" - ] + "required": ["id", "sessionID", "role", "time", "agent", "model"] }, "ProviderAuthError": { "type": "object", @@ -9287,16 +9079,10 @@ "type": "string" } }, - "required": [ - "providerID", - "message" - ] + "required": ["providerID", "message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "UnknownError": { "type": "object", @@ -9312,15 +9098,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageOutputLengthError": { "type": "object", @@ -9334,10 +9115,7 @@ "properties": {} } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageAbortedError": { "type": "object", @@ -9353,15 +9131,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "StructuredOutputError": { "type": "object", @@ -9380,16 +9153,10 @@ "type": "number" } }, - "required": [ - "message", - "retries" - ] + "required": ["message", "retries"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "ContextOverflowError": { "type": "object", @@ -9408,15 +9175,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "APIError": { "type": "object", @@ -9459,16 +9221,10 @@ } } }, - "required": [ - "message", - "isRetryable" - ] + "required": ["message", "isRetryable"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "AssistantMessage": { "type": "object", @@ -9493,9 +9249,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "error": { "anyOf": [ @@ -9547,10 +9301,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ] + "required": ["cwd", "root"] }, "summary": { "type": "boolean" @@ -9583,18 +9334,10 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] }, "structured": {}, "variant": { @@ -9643,15 +9386,10 @@ "$ref": "#/components/schemas/Message" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.removed": { "type": "object", @@ -9670,16 +9408,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID" - ] + "required": ["sessionID", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "TextPart": { "type": "object", @@ -9716,9 +9448,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -9728,13 +9458,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text" - ] + "required": ["id", "sessionID", "messageID", "type", "text"] }, "SubtaskPart": { "type": "object", @@ -9771,24 +9495,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "prompt", - "description", - "agent" - ] + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] }, "ReasoningPart": { "type": "object", @@ -9826,19 +9539,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "text", "time"] }, "FilePartSourceText": { "type": "object", @@ -9857,11 +9561,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] }, "FileSource": { "type": "object", @@ -9877,11 +9577,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "path" - ] + "required": ["text", "type", "path"] }, "Range": { "type": "object", @@ -9896,10 +9592,7 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] }, "end": { "type": "object", @@ -9911,16 +9604,10 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "SymbolSource": { "type": "object", @@ -9947,14 +9634,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "text", - "type", - "path", - "range", - "name", - "kind" - ] + "required": ["text", "type", "path", "range", "name", "kind"] }, "ResourceSource": { "type": "object", @@ -9973,12 +9653,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "clientName", - "uri" - ] + "required": ["text", "type", "clientName", "uri"] }, "FilePartSource": { "anyOf": [ @@ -10022,14 +9697,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "mime", - "url" - ] + "required": ["id", "sessionID", "messageID", "type", "mime", "url"] }, "ToolStatePending": { "type": "object", @@ -10049,11 +9717,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ] + "required": ["status", "input", "raw"] }, "ToolStateRunning": { "type": "object", @@ -10086,16 +9750,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "status", - "input", - "time" - ] + "required": ["status", "input", "time"] }, "ToolStateCompleted": { "type": "object", @@ -10137,10 +9795,7 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "attachments": { "type": "array", @@ -10149,14 +9804,7 @@ } } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ] + "required": ["status", "input", "output", "title", "metadata", "time"] }, "ToolStateError": { "type": "object", @@ -10192,18 +9840,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "error", - "time" - ] + "required": ["status", "input", "error", "time"] }, "ToolState": { "anyOf": [ @@ -10254,15 +9894,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "callID", - "tool", - "state" - ] + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] }, "StepStartPart": { "type": "object", @@ -10284,12 +9916,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type" - ] + "required": ["id", "sessionID", "messageID", "type"] }, "StepFinishPart": { "type": "object", @@ -10341,29 +9968,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "reason", - "cost", - "tokens" - ] + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] }, "SnapshotPart": { "type": "object", @@ -10385,13 +9996,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "snapshot" - ] + "required": ["id", "sessionID", "messageID", "type", "snapshot"] }, "PatchPart": { "type": "object", @@ -10419,14 +10024,7 @@ } } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "hash", - "files" - ] + "required": ["id", "sessionID", "messageID", "type", "hash", "files"] }, "AgentPart": { "type": "object", @@ -10464,20 +10062,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "name" - ] + "required": ["id", "sessionID", "messageID", "type", "name"] }, "RetryPart": { "type": "object", @@ -10508,20 +10096,10 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "attempt", - "error", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] }, "CompactionPart": { "type": "object", @@ -10546,13 +10124,7 @@ "type": "boolean" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "auto" - ] + "required": ["id", "sessionID", "messageID", "type", "auto"] }, "Part": { "anyOf": [ @@ -10608,15 +10180,10 @@ "$ref": "#/components/schemas/Part" } }, - "required": [ - "part" - ] + "required": ["part"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.delta": { "type": "object", @@ -10644,19 +10211,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID", - "field", - "delta" - ] + "required": ["sessionID", "messageID", "partID", "field", "delta"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.removed": { "type": "object", @@ -10678,17 +10236,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID" - ] + "required": ["sessionID", "messageID", "partID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionRequest": { "type": "object", @@ -10733,20 +10284,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "permission", - "patterns", - "metadata", - "always" - ] + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] }, "Event.permission.asked": { "type": "object", @@ -10759,10 +10300,7 @@ "$ref": "#/components/schemas/PermissionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.permission.replied": { "type": "object", @@ -10782,24 +10320,13 @@ }, "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "sessionID", - "requestID", - "reply" - ] + "required": ["sessionID", "requestID", "reply"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "SessionStatus": { "anyOf": [ @@ -10811,9 +10338,7 @@ "const": "idle" } }, - "required": [ - "type" - ] + "required": ["type"] }, { "type": "object", @@ -10832,12 +10357,7 @@ "type": "number" } }, - "required": [ - "type", - "attempt", - "message", - "next" - ] + "required": ["type", "attempt", "message", "next"] }, { "type": "object", @@ -10847,9 +10367,7 @@ "const": "busy" } }, - "required": [ - "type" - ] + "required": ["type"] } ] }, @@ -10870,16 +10388,10 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": [ - "sessionID", - "status" - ] + "required": ["sessionID", "status"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.idle": { "type": "object", @@ -10895,15 +10407,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionOption": { "type": "object", @@ -10921,10 +10428,7 @@ "type": "string" } }, - "required": [ - "label", - "description" - ] + "required": ["label", "description"] }, "QuestionInfo": { "type": "object", @@ -10953,11 +10457,7 @@ "type": "boolean" } }, - "required": [ - "question", - "header", - "options" - ] + "required": ["question", "header", "options"] }, "QuestionRequest": { "type": "object", @@ -10987,17 +10487,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "questions" - ] + "required": ["id", "sessionID", "questions"] }, "Event.question.asked": { "type": "object", @@ -11010,10 +10503,7 @@ "$ref": "#/components/schemas/QuestionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionAnswer": { "type": "array", @@ -11044,17 +10534,10 @@ } } }, - "required": [ - "sessionID", - "requestID", - "answers" - ] + "required": ["sessionID", "requestID", "answers"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.question.rejected": { "type": "object", @@ -11073,16 +10556,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "requestID" - ] + "required": ["sessionID", "requestID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.compacted": { "type": "object", @@ -11098,15 +10575,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.watcher.updated": { "type": "object", @@ -11138,16 +10610,10 @@ ] } }, - "required": [ - "file", - "event" - ] + "required": ["file", "event"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Todo": { "type": "object", @@ -11165,11 +10631,7 @@ "type": "string" } }, - "required": [ - "content", - "status", - "priority" - ] + "required": ["content", "status", "priority"] }, "Event.todo.updated": { "type": "object", @@ -11191,16 +10653,10 @@ } } }, - "required": [ - "sessionID", - "todos" - ] + "required": ["sessionID", "todos"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.prompt.append": { "type": "object", @@ -11216,15 +10672,10 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.command.execute": { "type": "object", @@ -11265,15 +10716,10 @@ ] } }, - "required": [ - "command" - ] + "required": ["command"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.toast.show": { "type": "object", @@ -11293,12 +10739,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -11306,16 +10747,10 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.session.select": { "type": "object", @@ -11333,15 +10768,10 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.tools.changed": { "type": "object", @@ -11357,15 +10787,10 @@ "type": "string" } }, - "required": [ - "server" - ] + "required": ["server"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.browser.open.failed": { "type": "object", @@ -11384,16 +10809,10 @@ "type": "string" } }, - "required": [ - "mcpName", - "url" - ] + "required": ["mcpName", "url"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.command.executed": { "type": "object", @@ -11420,26 +10839,14 @@ "pattern": "^msg.*" } }, - "required": [ - "name", - "sessionID", - "arguments", - "messageID" - ] + "required": ["name", "sessionID", "arguments", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionAction": { "type": "string", - "enum": [ - "allow", - "deny", - "ask" - ] + "enum": ["allow", "deny", "ask"] }, "PermissionRule": { "type": "object", @@ -11454,11 +10861,7 @@ "$ref": "#/components/schemas/PermissionAction" } }, - "required": [ - "permission", - "pattern", - "action" - ] + "required": ["permission", "pattern", "action"] }, "PermissionRuleset": { "type": "array", @@ -11508,11 +10911,7 @@ } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -11521,9 +10920,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -11547,10 +10944,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -11571,20 +10965,10 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time"] }, "Event.session.created": { "type": "object", @@ -11600,15 +10984,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.updated": { "type": "object", @@ -11624,15 +11003,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.deleted": { "type": "object", @@ -11648,15 +11022,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.diff": { "type": "object", @@ -11678,16 +11047,10 @@ } } }, - "required": [ - "sessionID", - "diff" - ] + "required": ["sessionID", "diff"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.error": { "type": "object", @@ -11730,10 +11093,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.open": { "type": "object", @@ -11749,15 +11109,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.close": { "type": "object", @@ -11774,23 +11129,13 @@ }, "reason": { "type": "string", - "enum": [ - "completed", - "error", - "interrupted" - ] + "enum": ["completed", "error", "interrupted"] } }, - "required": [ - "sessionID", - "reason" - ] + "required": ["sessionID", "reason"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.vcs.branch.updated": { "type": "object", @@ -11808,10 +11153,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.ready": { "type": "object", @@ -11827,15 +11169,10 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.failed": { "type": "object", @@ -11851,15 +11188,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Pty": { "type": "object", @@ -11885,24 +11217,13 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited" - ] + "enum": ["running", "exited"] }, "pid": { "type": "number" } }, - "required": [ - "id", - "title", - "command", - "args", - "cwd", - "status", - "pid" - ] + "required": ["id", "title", "command", "args", "cwd", "status", "pid"] }, "Event.pty.created": { "type": "object", @@ -11918,15 +11239,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.updated": { "type": "object", @@ -11942,15 +11258,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.exited": { "type": "object", @@ -11970,16 +11281,10 @@ "type": "number" } }, - "required": [ - "id", - "exitCode" - ] + "required": ["id", "exitCode"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.deleted": { "type": "object", @@ -11996,15 +11301,10 @@ "pattern": "^pty.*" } }, - "required": [ - "id" - ] + "required": ["id"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.ready": { "type": "object", @@ -12023,16 +11323,10 @@ "type": "string" } }, - "required": [ - "name", - "branch" - ] + "required": ["name", "branch"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.failed": { "type": "object", @@ -12048,15 +11342,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event": { "anyOf": [ @@ -12216,20 +11505,12 @@ "$ref": "#/components/schemas/Event" } }, - "required": [ - "directory", - "payload" - ] + "required": ["directory", "payload"] }, "LogLevel": { "description": "Log level", "type": "string", - "enum": [ - "DEBUG", - "INFO", - "WARN", - "ERROR" - ] + "enum": ["DEBUG", "INFO", "WARN", "ERROR"] }, "ServerConfig": { "description": "Server configuration for kilo serve and web commands", @@ -12267,11 +11548,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] + "enum": ["ask", "allow", "deny"] }, { "type": "null" @@ -12414,11 +11691,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "hidden": { "description": "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", @@ -12440,15 +11713,7 @@ }, { "type": "string", - "enum": [ - "primary", - "secondary", - "accent", - "success", - "warning", - "error", - "info" - ] + "enum": ["primary", "secondary", "accent", "success", "warning", "error", "info"] } ] }, @@ -12534,15 +11799,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -12578,16 +11838,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -12602,10 +11856,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -12614,61 +11865,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, + "ai_sdk_provider": { + "type": "string", + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] + }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -12804,10 +12033,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "command" - ], + "required": ["type", "command"], "additionalProperties": false }, "McpOAuthConfig": { @@ -12873,19 +12099,13 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "url" - ], + "required": ["type", "url"], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": [ - "auto", - "stretch" - ] + "enum": ["auto", "stretch"] }, "Config": { "type": "object", @@ -12925,9 +12145,7 @@ "type": "boolean" } }, - "required": [ - "template" - ] + "required": ["template"] } }, "skills": { @@ -12973,11 +12191,7 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": [ - "manual", - "auto", - "disabled" - ] + "enum": ["manual", "auto", "disabled"] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -13128,9 +12342,7 @@ "type": "boolean" } }, - "required": [ - "enabled" - ], + "required": ["enabled"], "additionalProperties": false } ] @@ -13200,9 +12412,7 @@ "const": true } }, - "required": [ - "disabled" - ] + "required": ["disabled"] }, { "type": "object", @@ -13239,9 +12449,7 @@ "additionalProperties": {} } }, - "required": [ - "command" - ] + "required": ["command"] } ] } @@ -13358,11 +12566,7 @@ "const": false } }, - "required": [ - "data", - "errors", - "success" - ] + "required": ["data", "errors", "success"] }, "OAuth": { "type": "object", @@ -13387,12 +12591,7 @@ "type": "string" } }, - "required": [ - "type", - "refresh", - "access", - "expires" - ] + "required": ["type", "refresh", "access", "expires"] }, "ApiAuth": { "type": "object", @@ -13405,10 +12604,7 @@ "type": "string" } }, - "required": [ - "type", - "key" - ] + "required": ["type", "key"] }, "WellKnownAuth": { "type": "object", @@ -13424,11 +12620,7 @@ "type": "string" } }, - "required": [ - "type", - "key", - "token" - ] + "required": ["type", "key", "token"] }, "Auth": { "anyOf": [ @@ -13457,15 +12649,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "Model": { "type": "object", @@ -13489,11 +12676,7 @@ "type": "string" } }, - "required": [ - "id", - "url", - "npm" - ] + "required": ["id", "url", "npm"] }, "name": { "type": "string" @@ -13535,13 +12718,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "output": { "type": "object", @@ -13562,13 +12739,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "interleaved": { "anyOf": [ @@ -13580,28 +12751,15 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ] + "required": ["field"] } ] } }, - "required": [ - "temperature", - "reasoning", - "attachment", - "toolcall", - "input", - "output", - "interleaved" - ] + "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"] }, "cost": { "type": "object", @@ -13622,10 +12780,7 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] }, "experimentalOver200K": { "type": "object", @@ -13646,24 +12801,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] }, "limit": { "type": "object", @@ -13678,19 +12822,11 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated", - "active" - ] + "enum": ["alpha", "beta", "deprecated", "active"] }, "options": { "type": "object", @@ -13729,17 +12865,14 @@ }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" + }, + "ai_sdk_provider": { + "type": "string", + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] } }, "required": [ @@ -13767,12 +12900,7 @@ }, "source": { "type": "string", - "enum": [ - "env", - "config", - "custom", - "api" - ] + "enum": ["env", "config", "custom", "api"] }, "env": { "type": "array", @@ -13800,14 +12928,7 @@ } } }, - "required": [ - "id", - "name", - "source", - "env", - "options", - "models" - ] + "required": ["id", "name", "source", "env", "options", "models"] }, "ToolIDs": { "type": "array", @@ -13826,11 +12947,7 @@ }, "parameters": {} }, - "required": [ - "id", - "description", - "parameters" - ] + "required": ["id", "description", "parameters"] }, "ToolList": { "type": "array", @@ -13890,15 +13007,7 @@ "type": "string" } }, - "required": [ - "id", - "type", - "branch", - "name", - "directory", - "extra", - "projectID" - ] + "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] }, "Worktree": { "type": "object", @@ -13913,11 +13022,7 @@ "type": "string" } }, - "required": [ - "name", - "branch", - "directory" - ] + "required": ["name", "branch", "directory"] }, "WorktreeCreateInput": { "type": "object", @@ -13938,9 +13043,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeResetInput": { "type": "object", @@ -13949,9 +13052,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeDiffItem": { "type": "object", @@ -13973,11 +13074,7 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] }, "tracked": { "type": "boolean" @@ -14017,10 +13114,7 @@ "type": "string" } }, - "required": [ - "id", - "worktree" - ] + "required": ["id", "worktree"] }, "GlobalSession": { "type": "object", @@ -14064,11 +13158,7 @@ } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -14077,9 +13167,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -14103,10 +13191,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -14127,9 +13212,7 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] }, "project": { "anyOf": [ @@ -14142,16 +13225,7 @@ ] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time", - "project" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] }, "McpResource": { "type": "object", @@ -14172,11 +13246,7 @@ "type": "string" } }, - "required": [ - "name", - "uri", - "client" - ] + "required": ["name", "uri", "client"] }, "TextPartInput": { "type": "object", @@ -14207,9 +13277,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -14219,10 +13287,7 @@ "additionalProperties": {} } }, - "required": [ - "type", - "text" - ] + "required": ["type", "text"] }, "FilePartInput": { "type": "object", @@ -14247,11 +13312,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] }, "AgentPartInput": { "type": "object", @@ -14283,17 +13344,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "type", - "name" - ] + "required": ["type", "name"] }, "SubtaskPartInput": { "type": "object", @@ -14324,21 +13378,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "type", - "prompt", - "description", - "agent" - ] + "required": ["type", "prompt", "description", "agent"] }, "ProviderAuthMethod": { "type": "object", @@ -14359,10 +13405,7 @@ "type": "string" } }, - "required": [ - "type", - "label" - ] + "required": ["type", "label"] }, "ProviderAuthAuthorization": { "type": "object", @@ -14386,11 +13429,7 @@ "type": "string" } }, - "required": [ - "url", - "method", - "instructions" - ] + "required": ["url", "method", "instructions"] }, "Symbol": { "type": "object", @@ -14411,17 +13450,10 @@ "$ref": "#/components/schemas/Range" } }, - "required": [ - "uri", - "range" - ] + "required": ["uri", "range"] } }, - "required": [ - "name", - "kind", - "location" - ] + "required": ["name", "kind", "location"] }, "FileNode": { "type": "object", @@ -14437,32 +13469,20 @@ }, "type": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] }, "ignored": { "type": "boolean" } }, - "required": [ - "name", - "path", - "absolute", - "type", - "ignored" - ] + "required": ["name", "path", "absolute", "type", "ignored"] }, "FileContent": { "type": "object", "properties": { "type": { "type": "string", - "enum": [ - "text", - "binary" - ] + "enum": ["text", "binary"] }, "content": { "type": "string" @@ -14509,24 +13529,14 @@ } } }, - "required": [ - "oldStart", - "oldLines", - "newStart", - "newLines", - "lines" - ] + "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] } }, "index": { "type": "string" } }, - "required": [ - "oldFileName", - "newFileName", - "hunks" - ] + "required": ["oldFileName", "newFileName", "hunks"] }, "encoding": { "type": "string", @@ -14536,10 +13546,7 @@ "type": "string" } }, - "required": [ - "type", - "content" - ] + "required": ["type", "content"] }, "File": { "type": "object", @@ -14559,19 +13566,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "path", - "added", - "removed", - "status" - ] + "required": ["path", "added", "removed", "status"] }, "MCPStatusConnected": { "type": "object", @@ -14581,9 +13579,7 @@ "const": "connected" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusDisabled": { "type": "object", @@ -14593,9 +13589,7 @@ "const": "disabled" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusFailed": { "type": "object", @@ -14608,10 +13602,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatusNeedsAuth": { "type": "object", @@ -14621,9 +13612,7 @@ "const": "needs_auth" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -14636,10 +13625,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatus": { "anyOf": [ @@ -14679,13 +13665,7 @@ "type": "string" } }, - "required": [ - "home", - "state", - "config", - "worktree", - "directory" - ] + "required": ["home", "state", "config", "worktree", "directory"] }, "VcsInfo": { "type": "object", @@ -14694,9 +13674,7 @@ "type": "string" } }, - "required": [ - "branch" - ] + "required": ["branch"] }, "Command": { "type": "object", @@ -14715,11 +13693,7 @@ }, "source": { "type": "string", - "enum": [ - "command", - "mcp", - "skill" - ] + "enum": ["command", "mcp", "skill"] }, "template": { "anyOf": [ @@ -14741,11 +13715,7 @@ } } }, - "required": [ - "name", - "template", - "hints" - ] + "required": ["name", "template", "hints"] }, "Agent": { "type": "object", @@ -14761,11 +13731,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "native": { "type": "boolean" @@ -14795,10 +13761,7 @@ "type": "string" } }, - "required": [ - "modelID", - "providerID" - ] + "required": ["modelID", "providerID"] }, "variant": { "type": "string" @@ -14819,12 +13782,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "name", - "mode", - "permission", - "options" - ] + "required": ["name", "mode", "permission", "options"] }, "LSPStatus": { "type": "object", @@ -14851,12 +13809,7 @@ ] } }, - "required": [ - "id", - "name", - "root", - "status" - ] + "required": ["id", "name", "root", "status"] }, "FormatterStatus": { "type": "object", @@ -14874,12 +13827,8 @@ "type": "boolean" } }, - "required": [ - "name", - "extensions", - "enabled" - ] + "required": ["name", "extensions", "enabled"] } } } -} \ No newline at end of file +} From 0cf7135a6e4c02470516fc46b8bf7565a4cdf7e3 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 27 Mar 2026 15:41:20 +0200 Subject: [PATCH 06/33] fix(cli): push wildcard to approved for session-scoped allow-all --- packages/opencode/src/permission/next.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 7aa9d73088e..281fc7de6ac 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -371,9 +371,7 @@ export namespace PermissionNext { return } - if (!input.sessionID) { - s.approved.push({ permission: "*", pattern: "*", action: "allow" }) - } + s.approved.push({ permission: "*", pattern: "*", action: "allow" }) if (input.requestID) { const existing = s.pending[input.requestID] From 93ce148f338b4853cde9b7aa660fd7cd163f6445 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 15:39:36 +0300 Subject: [PATCH 07/33] feat(cli): add kilo_local_recall tool and cross-project session search Add agent tool to search and read past conversations across all projects. Add --all and --search flags to 'kilo session list' CLI command. Add Ctrl+A toggle in TUI session list for all-projects view. --- packages/opencode/src/cli/cmd/session.ts | 98 +++++++++++-- .../cmd/tui/component/dialog-session-list.tsx | 50 ++++++- packages/opencode/src/tool/recall.ts | 131 ++++++++++++++++++ packages/opencode/src/tool/recall.txt | 12 ++ packages/opencode/src/tool/registry.ts | 2 + 5 files changed, 271 insertions(+), 22 deletions(-) create mode 100644 packages/opencode/src/tool/recall.ts create mode 100644 packages/opencode/src/tool/recall.txt diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index 8c770fdb65b..ec62ab93d69 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -73,22 +73,41 @@ export const SessionListCommand = cmd({ command: "list", describe: "list sessions", builder: (yargs: Argv) => { - return yargs - .option("max-count", { - alias: "n", - describe: "limit to N most recent sessions", - type: "number", - }) - .option("format", { - describe: "output format", - type: "string", - choices: ["table", "json"], - default: "table", - }) + return ( + yargs + .option("max-count", { + alias: "n", + describe: "limit to N most recent sessions", + type: "number", + }) + .option("format", { + describe: "output format", + type: "string", + choices: ["table", "json"], + default: "table", + }) + // kilocode_change start + .option("all", { + alias: "a", + describe: "list sessions from all projects", + type: "boolean", + default: false, + }) + .option("search", { + alias: "s", + describe: "filter sessions by title", + type: "string", + }) + ) + // kilocode_change end }, handler: async (args) => { await bootstrap(process.cwd(), async () => { - const sessions = [...Session.list({ roots: true, limit: args.maxCount })] + // kilocode_change start + const sessions = args.all + ? [...Session.listGlobal({ roots: true, limit: args.maxCount, search: args.search })] + : [...Session.list({ roots: true, limit: args.maxCount, search: args.search })] + // kilocode_change end if (sessions.length === 0) { return @@ -96,9 +115,17 @@ export const SessionListCommand = cmd({ let output: string if (args.format === "json") { - output = formatSessionJSON(sessions) + // kilocode_change start + output = args.all + ? formatGlobalSessionJSON(sessions as Session.GlobalInfo[]) + : formatSessionJSON(sessions as Session.Info[]) + // kilocode_change end } else { - output = formatSessionTable(sessions) + // kilocode_change start + output = args.all + ? formatGlobalSessionTable(sessions as Session.GlobalInfo[]) + : formatSessionTable(sessions as Session.Info[]) + // kilocode_change end } const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table" @@ -155,3 +182,44 @@ function formatSessionJSON(sessions: Session.Info[]): string { })) return JSON.stringify(jsonData, null, 2) } + +// kilocode_change start +function formatGlobalSessionTable(sessions: Session.GlobalInfo[]): string { + const lines: string[] = [] + + const maxIdWidth = Math.max(20, ...sessions.map((s) => s.id.length)) + const maxTitleWidth = Math.max(25, ...sessions.map((s) => s.title.length)) + const maxProjectWidth = Math.max( + 10, + ...sessions.map((s) => (s.project?.name ?? s.project?.worktree ?? "unknown").length), + ) + + const header = `Session ID${" ".repeat(maxIdWidth - 10)} Title${" ".repeat(maxTitleWidth - 5)} Project${" ".repeat(maxProjectWidth - 7)} Updated` + lines.push(header) + lines.push("─".repeat(header.length)) + for (const session of sessions) { + const truncatedTitle = Locale.truncate(session.title, maxTitleWidth) + const project = Locale.truncate(session.project?.name ?? session.project?.worktree ?? "unknown", maxProjectWidth) + const timeStr = Locale.todayTimeOrDateTime(session.time.updated) + const line = `${session.id.padEnd(maxIdWidth)} ${truncatedTitle.padEnd(maxTitleWidth)} ${project.padEnd(maxProjectWidth)} ${timeStr}` + lines.push(line) + } + + return lines.join(EOL) +} + +function formatGlobalSessionJSON(sessions: Session.GlobalInfo[]): string { + const jsonData = sessions.map((session) => ({ + id: session.id, + title: session.title, + updated: session.time.updated, + created: session.time.created, + projectId: session.projectID, + directory: session.directory, + project: session.project + ? { id: session.project.id, name: session.project.name, worktree: session.project.worktree } + : null, + })) + return JSON.stringify(jsonData, null, 2) +} +// kilocode_change end diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 775969bfcb3..28775e3b03c 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -23,12 +23,26 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) + const [global, setGlobal] = createSignal(false) // kilocode_change - const [searchResults] = createResource(search, async (query) => { - if (!query) return undefined - const result = await sdk.client.session.list({ search: query, limit: 30 }) - return result.data ?? [] - }) + // kilocode_change start + const [searchResults] = createResource( + () => ({ query: search(), global: global() }), + async ({ query, global: all }) => { + if (!query && !all) return undefined + if (all) { + const result = await sdk.client.experimental.session.list({ + search: query || undefined, + roots: true, + limit: 30, + }) + return result.data ?? [] + } + const result = await sdk.client.session.list({ search: query, limit: 30 }) + return result.data ?? [] + }, + ) + // kilocode_change end const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) @@ -36,6 +50,7 @@ export function DialogSessionList() { const options = createMemo(() => { const today = new Date().toDateString() + const all = global() // kilocode_change return sessions() .filter((x) => x.parentID === undefined) .toSorted((a, b) => b.time.updated - a.time.updated) @@ -48,8 +63,12 @@ export function DialogSessionList() { const isDeleting = toDelete() === x.id const status = sync.data.session_status?.[x.id] const isWorking = status?.type === "busy" + // kilocode_change start + const project = all && "project" in x ? (x as any).project : undefined + const suffix = project ? ` [${project.name ?? project.worktree ?? ""}]` : "" + // kilocode_change end return { - title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, + title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title + suffix, // kilocode_change bg: isDeleting ? theme.error : undefined, value: x.id, category, @@ -65,7 +84,7 @@ export function DialogSessionList() { return ( { + if (global()) return // kilocode_change if (toDelete() === option.value) { sdk.client.session.delete({ sessionID: option.value, @@ -98,10 +121,23 @@ export function DialogSessionList() { { keybind: keybind.all.session_rename?.[0], title: "rename", + // kilocode_change start + disabled: global(), + // kilocode_change end onTrigger: async (option) => { + if (global()) return // kilocode_change dialog.replace(() => ) }, }, + // kilocode_change start + { + keybind: { name: "a", ctrl: true, meta: false, shift: false, leader: false }, + title: global() ? "project" : "all", + onTrigger: async () => { + setGlobal((v) => !v) + }, + }, + // kilocode_change end ]} /> ) diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts new file mode 100644 index 00000000000..1b2f3c2ed95 --- /dev/null +++ b/packages/opencode/src/tool/recall.ts @@ -0,0 +1,131 @@ +// kilocode_change - new file +import z from "zod" +import { Tool } from "./tool" +import { Session } from "../session" +import { Instance } from "../project/instance" +import { Locale } from "../util/locale" +import DESCRIPTION from "./recall.txt" + +export const RecallTool = Tool.define("kilo_local_recall", { + description: DESCRIPTION, + parameters: z.object({ + mode: z.enum(["search", "read"]).describe("'search' to find sessions by title, 'read' to get a session transcript"), + query: z.string().optional().describe("Search query to match against session titles (required for search mode)"), + sessionID: z.string().optional().describe("Session ID to read the transcript of (required for read mode)"), + limit: z.number().optional().describe("Maximum number of search results to return (default: 20, max: 50)"), + }), + async execute(params, ctx) { + if (params.mode === "search") { + return search(params, ctx) + } + return read(params, ctx) + }, +}) + +async function search(params: { query?: string; limit?: number }, _ctx: Tool.Context) { + if (!params.query) { + throw new Error("The 'query' parameter is required when mode is 'search'") + } + + const limit = Math.min(params.limit ?? 20, 50) + const current = Instance.project.id + + const results: Array<{ + id: string + title: string + project: string + directory: string + updated: string + current: boolean + }> = [] + + for (const session of Session.listGlobal({ + search: params.query, + roots: true, + limit, + })) { + results.push({ + id: session.id, + title: session.title, + project: session.project?.name ?? session.project?.worktree ?? "unknown", + directory: session.directory, + updated: Locale.todayTimeOrDateTime(session.time.updated), + current: session.projectID === current, + }) + } + + if (results.length === 0) { + return { + title: `Search: "${params.query}" (no results)`, + output: `No sessions found matching "${params.query}".`, + metadata: {}, + } + } + + const lines = results.map( + (r) => + `- **${r.title}** (${r.current ? "current project" : r.project})\n ID: ${r.id} | Updated: ${r.updated} | Dir: ${r.directory}`, + ) + + return { + title: `Search: "${params.query}" (${results.length} results)`, + output: lines.join("\n"), + metadata: {}, + } +} + +async function read(params: { sessionID?: string }, ctx: Tool.Context) { + if (!params.sessionID) { + throw new Error("The 'sessionID' parameter is required when mode is 'read'") + } + + const session = await Session.get(params.sessionID) + const cross = session.projectID !== Instance.project.id + + if (cross) { + await ctx.ask({ + permission: "recall", + patterns: [session.directory], + always: [session.directory], + metadata: { + sessionID: session.id, + title: session.title, + directory: session.directory, + }, + }) + } + + const msgs = await Session.messages({ sessionID: session.id }) + const lines: string[] = [ + `# Session: ${session.title}`, + `Project: ${session.directory}`, + `Created: ${Locale.todayTimeOrDateTime(session.time.created)}`, + "", + ] + + for (const msg of msgs) { + if (msg.info.role === "user") { + lines.push("## User") + for (const part of msg.parts) { + if (part.type === "text") lines.push(part.text) + } + lines.push("") + } + if (msg.info.role === "assistant") { + lines.push("## Assistant") + for (const part of msg.parts) { + if (part.type === "text") lines.push(part.text) + if (part.type === "tool" && part.state.status === "completed") { + lines.push(`[Tool: ${part.tool}] ${part.state.title}`) + } + } + lines.push("") + } + } + + return { + title: `Read: ${session.title}`, + output: lines.join("\n"), + metadata: {}, + } +} diff --git a/packages/opencode/src/tool/recall.txt b/packages/opencode/src/tool/recall.txt new file mode 100644 index 00000000000..e9d10b88045 --- /dev/null +++ b/packages/opencode/src/tool/recall.txt @@ -0,0 +1,12 @@ +Search and read past conversations from any project on this machine. Use this to recall previous work, find how something was implemented before, or retrieve context from other projects. + +Two modes: +1. **Search** - Find sessions by title keyword across all projects. Returns a list of matching sessions with their project, title, and last updated time. Use this first to locate relevant conversations. +2. **Read** - Retrieve the full transcript of a specific session by ID. Returns the conversation messages (user prompts and assistant responses) so you can understand what was discussed and done. + +Usage notes: + - Search matches against session titles using case-insensitive substring matching + - Results include sessions from ALL projects on this machine, not just the current one + - Reading a session from a different project requires permission + - Use search mode first to find session IDs, then read mode to get the full conversation + - Session transcripts can be large; prefer searching first to narrow down which session to read diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 3c05ef92a00..b888d760b25 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -29,6 +29,7 @@ import { LspTool } from "./lsp" import { Truncate } from "./truncation" import { ApplyPatchTool } from "./apply_patch" +import { RecallTool } from "./recall" // kilocode_change import { Glob } from "../util/glob" import { pathToFileURL } from "url" @@ -118,6 +119,7 @@ export namespace ToolRegistry { CodeSearchTool, ...(config.experimental?.codebase_search === true ? [CodebaseSearchTool] : []), // kilocode_change SkillTool, + RecallTool, // kilocode_change ApplyPatchTool, ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), From 2e1ff4a4825093daa3a493068a0008401de5d94f Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 15:47:22 +0300 Subject: [PATCH 08/33] fix(cli): address review findings for recall tool - Add 200 message limit to read mode to prevent large memory allocation - Improve not-found error to guide LLM to search first - Gate RecallTool behind config.experimental.recall flag - Replace `as any` with typed cast in TUI dialog --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 3 ++- packages/opencode/src/config/config.ts | 1 + packages/opencode/src/tool/recall.ts | 6 ++++-- packages/opencode/src/tool/registry.ts | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 28775e3b03c..886e62ff3f3 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -64,7 +64,8 @@ export function DialogSessionList() { const status = sync.data.session_status?.[x.id] const isWorking = status?.type === "busy" // kilocode_change start - const project = all && "project" in x ? (x as any).project : undefined + const project = + all && "project" in x ? (x as { project?: { name?: string; worktree?: string } | null }).project : undefined const suffix = project ? ` [${project.name ?? project.worktree ?? ""}]` : "" // kilocode_change end return { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5b3e3b28de4..3bc2003d35c 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1315,6 +1315,7 @@ export namespace Config { disable_paste_summary: z.boolean().optional(), batch_tool: z.boolean().optional().describe("Enable the batch tool"), codebase_search: z.boolean().optional().describe("Enable AI-powered codebase search"), // kilocode_change + recall: z.boolean().optional().describe("Enable cross-project session recall tool"), // kilocode_change // kilocode_change start - enable telemetry by default openTelemetry: z.boolean().default(true).describe("Enable telemetry. Set to false to opt-out."), // kilocode_change end diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index 1b2f3c2ed95..e46cb98a898 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -79,7 +79,9 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { throw new Error("The 'sessionID' parameter is required when mode is 'read'") } - const session = await Session.get(params.sessionID) + const session = await Session.get(params.sessionID).catch(() => { + throw new Error(`Session "${params.sessionID}" not found. Use search mode first to find valid session IDs.`) + }) const cross = session.projectID !== Instance.project.id if (cross) { @@ -95,7 +97,7 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { }) } - const msgs = await Session.messages({ sessionID: session.id }) + const msgs = await Session.messages({ sessionID: session.id, limit: 200 }) const lines: string[] = [ `# Session: ${session.title}`, `Project: ${session.directory}`, diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index b888d760b25..c95167125f6 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -119,7 +119,7 @@ export namespace ToolRegistry { CodeSearchTool, ...(config.experimental?.codebase_search === true ? [CodebaseSearchTool] : []), // kilocode_change SkillTool, - RecallTool, // kilocode_change + ...(config.experimental?.recall === true ? [RecallTool] : []), // kilocode_change ApplyPatchTool, ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), From 117479ccee935a90eeaa898d32486beb51f4de55 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 16:03:33 +0300 Subject: [PATCH 09/33] fix(cli): always register local recall tool Avoid hiding session recall behind an opt-in flag so agents can reliably find prior debugging context. --- packages/opencode/src/config/config.ts | 1 - packages/opencode/src/tool/registry.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3bc2003d35c..5b3e3b28de4 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1315,7 +1315,6 @@ export namespace Config { disable_paste_summary: z.boolean().optional(), batch_tool: z.boolean().optional().describe("Enable the batch tool"), codebase_search: z.boolean().optional().describe("Enable AI-powered codebase search"), // kilocode_change - recall: z.boolean().optional().describe("Enable cross-project session recall tool"), // kilocode_change // kilocode_change start - enable telemetry by default openTelemetry: z.boolean().default(true).describe("Enable telemetry. Set to false to opt-out."), // kilocode_change end diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index c95167125f6..b888d760b25 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -119,7 +119,7 @@ export namespace ToolRegistry { CodeSearchTool, ...(config.experimental?.codebase_search === true ? [CodebaseSearchTool] : []), // kilocode_change SkillTool, - ...(config.experimental?.recall === true ? [RecallTool] : []), // kilocode_change + RecallTool, // kilocode_change ApplyPatchTool, ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), From 7d3f29953da0aeb52887516780ca3300be586f96 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 16:34:03 +0300 Subject: [PATCH 10/33] fix(cli): show repo roots in global session list Replace long absolute project paths in the all-project session picker so cross-project browsing stays readable in the TUI. --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 886e62ff3f3..2b412ae753d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,6 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" +import path from "path" import { createMemo, createSignal, createResource, onMount, Show } from "solid-js" import { Locale } from "@/util/locale" import { useKeybind } from "../context/keybind" @@ -65,8 +66,9 @@ export function DialogSessionList() { const isWorking = status?.type === "busy" // kilocode_change start const project = - all && "project" in x ? (x as { project?: { name?: string; worktree?: string } | null }).project : undefined - const suffix = project ? ` [${project.name ?? project.worktree ?? ""}]` : "" + all && "project" in x ? (x as { project?: { name?: string; worktree: string } | null }).project : undefined + const root = project?.name ?? (project ? path.basename(project.worktree) || project.worktree : "") + const suffix = root ? ` [${root}]` : "" // kilocode_change end return { title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title + suffix, // kilocode_change From 75231b3254c95aaff9e27cd571563786c27cbab6 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 18:03:40 +0300 Subject: [PATCH 11/33] fix(cli): gate recall search --- packages/opencode/src/tool/recall.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index e46cb98a898..eb2b8a8069f 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -22,7 +22,7 @@ export const RecallTool = Tool.define("kilo_local_recall", { }, }) -async function search(params: { query?: string; limit?: number }, _ctx: Tool.Context) { +async function search(params: { query?: string; limit?: number }, ctx: Tool.Context) { if (!params.query) { throw new Error("The 'query' parameter is required when mode is 'search'") } @@ -54,6 +54,19 @@ async function search(params: { query?: string; limit?: number }, _ctx: Tool.Con }) } + const dirs = [...new Set(results.filter((r) => !r.current).map((r) => r.directory))] + if (dirs.length > 0) { + await ctx.ask({ + permission: "recall", + patterns: dirs, + always: dirs, + metadata: { + mode: "search", + query: params.query, + }, + }) + } + if (results.length === 0) { return { title: `Search: "${params.query}" (no results)`, From 29ec6e401796892097068c1f9f317636d1d86091 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 18:08:34 +0300 Subject: [PATCH 12/33] fix(cli): block cross project open --- .../cmd/tui/component/dialog-session-list.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 2b412ae753d..b43e3779332 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -9,9 +9,9 @@ import { useKeybind } from "../context/keybind" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" import { DialogSessionRename } from "./dialog-session-rename" -import { useKV } from "../context/kv" import { createDebouncedSignal } from "../util/signal" import { Spinner } from "./spinner" +import { useToast } from "../ui/toast" export function DialogSessionList() { const dialog = useDialog() @@ -20,7 +20,7 @@ export function DialogSessionList() { const keybind = useKeybind() const { theme } = useTheme() const sdk = useSDK() - const kv = useKV() + const toast = useToast() const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) @@ -96,6 +96,20 @@ export function DialogSessionList() { setToDelete(undefined) }} onSelect={(option) => { + const item = sessions().find((x) => x.id === option.value) + const cross = + global() && + item && + "project" in item && + item.project?.worktree !== undefined && + item.project.worktree !== sync.data.path.worktree + if (cross) { + toast.show({ + message: "Open this session from its own project", + variant: "error", + }) + return + } route.navigate({ type: "session", sessionID: option.value, From dd571e4b8b965447894c3d7d20e88248796a7b28 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 2 Apr 2026 18:10:15 +0300 Subject: [PATCH 13/33] fix(cli): read full recall --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 10 +++++----- packages/opencode/src/tool/recall.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index b43e3779332..f212d884823 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -97,12 +97,12 @@ export function DialogSessionList() { }} onSelect={(option) => { const item = sessions().find((x) => x.id === option.value) + const project = + global() && item && "project" in item + ? (item as { project?: { worktree?: string } | null }).project + : undefined const cross = - global() && - item && - "project" in item && - item.project?.worktree !== undefined && - item.project.worktree !== sync.data.path.worktree + global() && item && project?.worktree !== undefined && project.worktree !== sync.data.path.worktree if (cross) { toast.show({ message: "Open this session from its own project", diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index eb2b8a8069f..e472ce4ee80 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -110,7 +110,7 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { }) } - const msgs = await Session.messages({ sessionID: session.id, limit: 200 }) + const msgs = await Session.messages({ sessionID: session.id }) const lines: string[] = [ `# Session: ${session.title}`, `Project: ${session.directory}`, From e9fbf6b72aa465deb310f2e6383b1e7dc71ff9f9 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 3 Apr 2026 12:05:57 +0300 Subject: [PATCH 14/33] fix(cli): require recall permission prompts --- packages/opencode/src/agent/agent.ts | 1 + packages/opencode/src/tool/recall.ts | 46 ++++++++++++---------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 8bfbe786a21..27723206cb1 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -187,6 +187,7 @@ export namespace Agent { "*": "allow", bash, // kilocode_change doom_loop: "ask", + recall: "ask", // kilocode_change external_directory: { "*": "ask", ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])), diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index e472ce4ee80..064b771bf29 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -27,6 +27,16 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont throw new Error("The 'query' parameter is required when mode is 'search'") } + await ctx.ask({ + permission: "recall", + patterns: ["search"], + always: ["search"], + metadata: { + mode: "search", + query: params.query, + }, + }) + const limit = Math.min(params.limit ?? 20, 50) const current = Instance.project.id @@ -54,19 +64,6 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont }) } - const dirs = [...new Set(results.filter((r) => !r.current).map((r) => r.directory))] - if (dirs.length > 0) { - await ctx.ask({ - permission: "recall", - patterns: dirs, - always: dirs, - metadata: { - mode: "search", - query: params.query, - }, - }) - } - if (results.length === 0) { return { title: `Search: "${params.query}" (no results)`, @@ -95,20 +92,17 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { const session = await Session.get(params.sessionID).catch(() => { throw new Error(`Session "${params.sessionID}" not found. Use search mode first to find valid session IDs.`) }) - const cross = session.projectID !== Instance.project.id - if (cross) { - await ctx.ask({ - permission: "recall", - patterns: [session.directory], - always: [session.directory], - metadata: { - sessionID: session.id, - title: session.title, - directory: session.directory, - }, - }) - } + await ctx.ask({ + permission: "recall", + patterns: [session.directory], + always: [session.directory], + metadata: { + sessionID: session.id, + title: session.title, + directory: session.directory, + }, + }) const msgs = await Session.messages({ sessionID: session.id }) const lines: string[] = [ From 5ddd2167fc290d2fe2cb007f6cb4e47803d33686 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 3 Apr 2026 22:07:11 +0300 Subject: [PATCH 15/33] fix: scope recall sessions to project worktrees Keep recall and the all-sessions picker focused on the current repo while still surfacing sessions from sibling worktrees. --- .../cmd/tui/component/dialog-session-list.tsx | 75 ++++++------ .../tui/component/dialog-session-rename.tsx | 14 ++- .../src/server/routes/experimental.ts | 3 + packages/opencode/src/session/index.ts | 7 ++ packages/opencode/src/tool/recall.ts | 22 ++-- packages/opencode/src/tool/recall.txt | 8 +- .../server/experimental-session-list.test.ts | 88 ++++++++++++++ .../test/server/global-session-list.test.ts | 62 +++++++++- packages/opencode/test/tool/recall.test.ts | 111 ++++++++++++++++++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 4 + packages/sdk/openapi.json | 8 ++ 12 files changed, 339 insertions(+), 65 deletions(-) create mode 100644 packages/opencode/test/server/experimental-session-list.test.ts create mode 100644 packages/opencode/test/tool/recall.test.ts diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index f212d884823..b06903569cf 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -3,7 +3,7 @@ import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" import path from "path" -import { createMemo, createSignal, createResource, onMount, Show } from "solid-js" +import { createMemo, createSignal, createResource, onMount } from "solid-js" import { Locale } from "@/util/locale" import { useKeybind } from "../context/keybind" import { useTheme } from "../context/theme" @@ -11,7 +11,6 @@ import { useSDK } from "../context/sdk" import { DialogSessionRename } from "./dialog-session-rename" import { createDebouncedSignal } from "../util/signal" import { Spinner } from "./spinner" -import { useToast } from "../ui/toast" export function DialogSessionList() { const dialog = useDialog() @@ -20,26 +19,30 @@ export function DialogSessionList() { const keybind = useKeybind() const { theme } = useTheme() const sdk = useSDK() - const toast = useToast() const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) const [global, setGlobal] = createSignal(false) // kilocode_change // kilocode_change start - const [searchResults] = createResource( + const [searchResults, searchActions] = createResource( () => ({ query: search(), global: global() }), async ({ query, global: all }) => { if (!query && !all) return undefined if (all) { - const result = await sdk.client.experimental.session.list({ - search: query || undefined, - roots: true, - limit: 30, - }) + const project = await sdk.client.project.current({}, { throwOnError: true }) + const result = await sdk.client.experimental.session.list( + { + projectID: project.data?.id, + search: query || undefined, + roots: true, + limit: 30, + }, + { throwOnError: true }, + ) return result.data ?? [] } - const result = await sdk.client.session.list({ search: query, limit: 30 }) + const result = await sdk.client.session.list({ search: query || undefined, limit: 30 }, { throwOnError: true }) return result.data ?? [] }, ) @@ -47,7 +50,10 @@ export function DialogSessionList() { const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) - const sessions = createMemo(() => searchResults() ?? sync.data.session) + const sessions = createMemo(() => { + if (global() || search()) return searchResults() ?? [] // kilocode_change + return searchResults() ?? sync.data.session + }) const options = createMemo(() => { const today = new Date().toDateString() @@ -64,12 +70,8 @@ export function DialogSessionList() { const isDeleting = toDelete() === x.id const status = sync.data.session_status?.[x.id] const isWorking = status?.type === "busy" - // kilocode_change start - const project = - all && "project" in x ? (x as { project?: { name?: string; worktree: string } | null }).project : undefined - const root = project?.name ?? (project ? path.basename(project.worktree) || project.worktree : "") + const root = all ? path.basename(x.directory) || x.directory : "" // kilocode_change const suffix = root ? ` [${root}]` : "" - // kilocode_change end return { title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title + suffix, // kilocode_change bg: isDeleting ? theme.error : undefined, @@ -87,7 +89,7 @@ export function DialogSessionList() { return ( { - const item = sessions().find((x) => x.id === option.value) - const project = - global() && item && "project" in item - ? (item as { project?: { worktree?: string } | null }).project - : undefined - const cross = - global() && item && project?.worktree !== undefined && project.worktree !== sync.data.path.worktree - if (cross) { - toast.show({ - message: "Open this session from its own project", - variant: "error", - }) - return - } route.navigate({ type: "session", sessionID: option.value, @@ -120,16 +108,13 @@ export function DialogSessionList() { { keybind: keybind.all.session_delete?.[0], title: "delete", - // kilocode_change start - disabled: global(), - // kilocode_change end onTrigger: async (option) => { - if (global()) return // kilocode_change if (toDelete() === option.value) { - sdk.client.session.delete({ + await sdk.client.session.delete({ sessionID: option.value, }) setToDelete(undefined) + if (global() || search()) void searchActions.refetch() // kilocode_change return } setToDelete(option.value) @@ -138,19 +123,25 @@ export function DialogSessionList() { { keybind: keybind.all.session_rename?.[0], title: "rename", - // kilocode_change start - disabled: global(), - // kilocode_change end onTrigger: async (option) => { - if (global()) return // kilocode_change - dialog.replace(() => ) + const item = sessions().find((x) => x.id === option.value) + dialog.replace(() => ( + { + if (global() || search()) void searchActions.refetch() // kilocode_change + }} + /> + )) }, }, // kilocode_change start { keybind: { name: "a", ctrl: true, meta: false, shift: false, leader: false }, - title: global() ? "project" : "all", + title: global() ? "recent" : "all", onTrigger: async () => { + setToDelete(undefined) setGlobal((v) => !v) }, }, diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx index 141340d5562..be4b8f65dd2 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx @@ -6,6 +6,8 @@ import { useSDK } from "../context/sdk" interface DialogSessionRenameProps { session: string + title?: string // kilocode_change + onConfirm?: () => void // kilocode_change } export function DialogSessionRename(props: DialogSessionRenameProps) { @@ -17,12 +19,14 @@ export function DialogSessionRename(props: DialogSessionRenameProps) { return ( { - sdk.client.session.update({ - sessionID: props.session, - title: value, - }) + sdk.client.session + .update({ + sessionID: props.session, + title: value, + }) + .then(() => props.onConfirm?.()) // kilocode_change dialog.clear() }} onCancel={() => dialog.clear()} diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index 9f1227b6d94..a3672d7b694 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -326,6 +326,8 @@ export const ExperimentalRoutes = lazy(() => validator( "query", z.object({ + // kilocode_change + projectID: z.string().optional().meta({ description: "Filter sessions by project ID" }), directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), start: z.coerce @@ -346,6 +348,7 @@ export const ExperimentalRoutes = lazy(() => const limit = query.limit ?? 100 const sessions: Session.GlobalInfo[] = [] for await (const session of Session.listGlobal({ + projectID: query.projectID, // kilocode_change directory: query.directory, roots: query.roots, start: query.start, diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index b2912e8223e..504c6f5719f 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -622,6 +622,7 @@ export namespace Session { } export function* listGlobal(input?: { + projectID?: string directory?: string roots?: boolean start?: number @@ -632,6 +633,12 @@ export namespace Session { }) { const conditions: SQL[] = [] + // kilocode_change start + if (input?.projectID) { + conditions.push(eq(SessionTable.project_id, input.projectID)) + } + // kilocode_change end + if (input?.directory) { // kilocode_change start: vscode uri.fsPath gives lowercase drive letter on Windows; resolve() canonicalises to match stored path conditions.push(eq(SessionTable.directory, Filesystem.resolve(input.directory))) diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index 064b771bf29..502603467fa 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -1,7 +1,6 @@ // kilocode_change - new file import z from "zod" import { Tool } from "./tool" -import { Session } from "../session" import { Instance } from "../project/instance" import { Locale } from "../util/locale" import DESCRIPTION from "./recall.txt" @@ -39,17 +38,17 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont const limit = Math.min(params.limit ?? 20, 50) const current = Instance.project.id + const { Session } = await import("../session/index") // kilocode_change const results: Array<{ id: string title: string - project: string directory: string updated: string - current: boolean }> = [] for (const session of Session.listGlobal({ + projectID: current, // kilocode_change search: params.query, roots: true, limit, @@ -57,10 +56,8 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont results.push({ id: session.id, title: session.title, - project: session.project?.name ?? session.project?.worktree ?? "unknown", directory: session.directory, updated: Locale.todayTimeOrDateTime(session.time.updated), - current: session.projectID === current, }) } @@ -72,10 +69,7 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont } } - const lines = results.map( - (r) => - `- **${r.title}** (${r.current ? "current project" : r.project})\n ID: ${r.id} | Updated: ${r.updated} | Dir: ${r.directory}`, - ) + const lines = results.map((r) => `- **${r.title}**\n ID: ${r.id} | Updated: ${r.updated} | Dir: ${r.directory}`) return { title: `Search: "${params.query}" (${results.length} results)`, @@ -89,9 +83,17 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { throw new Error("The 'sessionID' parameter is required when mode is 'read'") } + const { Session } = await import("../session/index") // kilocode_change const session = await Session.get(params.sessionID).catch(() => { throw new Error(`Session "${params.sessionID}" not found. Use search mode first to find valid session IDs.`) }) + // kilocode_change start + if (session.projectID !== Instance.project.id) { + throw new Error( + `Session "${params.sessionID}" belongs to a different project and cannot be read from this workspace.`, + ) + } + // kilocode_change end await ctx.ask({ permission: "recall", @@ -107,7 +109,7 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { const msgs = await Session.messages({ sessionID: session.id }) const lines: string[] = [ `# Session: ${session.title}`, - `Project: ${session.directory}`, + `Directory: ${session.directory}`, `Created: ${Locale.todayTimeOrDateTime(session.time.created)}`, "", ] diff --git a/packages/opencode/src/tool/recall.txt b/packages/opencode/src/tool/recall.txt index e9d10b88045..fa73e81dc33 100644 --- a/packages/opencode/src/tool/recall.txt +++ b/packages/opencode/src/tool/recall.txt @@ -1,12 +1,12 @@ -Search and read past conversations from any project on this machine. Use this to recall previous work, find how something was implemented before, or retrieve context from other projects. +Search and read past conversations from the current project on this machine, including its git worktrees. Use this to recall previous work, find how something was implemented before, or retrieve context from another worktree in the same repo. Two modes: -1. **Search** - Find sessions by title keyword across all projects. Returns a list of matching sessions with their project, title, and last updated time. Use this first to locate relevant conversations. +1. **Search** - Find sessions by title keyword in the current project and its worktrees. Returns a list of matching sessions with their title, directory, and last updated time. Use this first to locate relevant conversations. 2. **Read** - Retrieve the full transcript of a specific session by ID. Returns the conversation messages (user prompts and assistant responses) so you can understand what was discussed and done. Usage notes: - Search matches against session titles using case-insensitive substring matching - - Results include sessions from ALL projects on this machine, not just the current one - - Reading a session from a different project requires permission + - Results are limited to the current project/worktree family + - Reading a session from a different project is rejected - Use search mode first to find session IDs, then read mode to get the full conversation - Session transcripts can be large; prefer searching first to narrow down which session to read diff --git a/packages/opencode/test/server/experimental-session-list.test.ts b/packages/opencode/test/server/experimental-session-list.test.ts new file mode 100644 index 00000000000..db8dcde9403 --- /dev/null +++ b/packages/opencode/test/server/experimental-session-list.test.ts @@ -0,0 +1,88 @@ +import { afterEach, describe, expect, mock, test } from "bun:test" +import { $ } from "bun" +import path from "path" +import { Config } from "../../src/config/config" +import { Instance } from "../../src/project/instance" +import { Log } from "../../src/util/log" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +mock.module("@/kilo-sessions/remote-sender", () => ({ + RemoteSender: { + create() { + return { + handle() {}, + dispose() {}, + } + }, + }, +})) + +Log.init({ print: false }) + +afterEach(async () => { + await resetDatabase() +}) + +describe("experimental.session.list", () => { + test("filters sessions by project ID across worktrees", async () => { + await using first = await tmpdir({ git: true }) + await using second = await tmpdir({ git: true }) + const worktree = path.join(first.path, "..", path.basename(first.path) + "-worktree") + + try { + await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + + const share = Config.get + Config.get = async () => ({ share: "manual" }) as Awaited> + + try { + const { Server } = await import("../../src/server/server") + const { Session } = await import("../../src/session/index") + const root = await Instance.provide({ + directory: first.path, + fn: async () => ({ + app: Server.App(), + project: await Server.App().request("/project/current", { + headers: { "x-kilo-directory": first.path }, + }), + session: await Session.create({ title: "root-session" }), + }), + }) + + const branch = await Instance.provide({ + directory: worktree, + fn: async () => Session.create({ title: "worktree-session" }), + }) + + await Instance.provide({ + directory: second.path, + fn: async () => Session.create({ title: "other-project-session" }), + }) + + const app = root.app + const project = await root.project.json() + const response = await app.request( + `/experimental/session?projectID=${encodeURIComponent(project.id)}&roots=true`, + { + headers: { "x-kilo-directory": first.path }, + }, + ) + + expect(response.status).toBe(200) + const body = await response.json() + const ids = body.map((item: { id: string }) => item.id) + const dirs = body.map((item: { directory: string }) => item.directory) + + expect(ids).toContain(root.session.id) + expect(ids).toContain(branch.id) + expect(dirs).toContain(worktree) + expect(body.some((item: { title: string }) => item.title === "other-project-session")).toBe(false) + } finally { + Config.get = share + } + } finally { + await $`git worktree remove ${worktree}`.cwd(first.path).quiet().nothrow() + } + }) +}) diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 05d6de04b1b..8eb20ea549c 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -1,14 +1,32 @@ -import { describe, expect, test } from "bun:test" +import { $ } from "bun" +import { afterEach, describe, expect, mock, test } from "bun:test" +import path from "path" import { Instance } from "../../src/project/instance" import { Project } from "../../src/project/project" -import { Session } from "../../src/session" import { Log } from "../../src/util/log" +import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" +mock.module("@/kilo-sessions/remote-sender", () => ({ + RemoteSender: { + create() { + return { + handle() {}, + dispose() {}, + } + }, + }, +})) + Log.init({ print: false }) +afterEach(async () => { + await resetDatabase() +}) + describe("Session.listGlobal", () => { test("lists sessions across projects with project metadata", async () => { + const { Session } = await import("../../src/session/index") await using first = await tmpdir({ git: true }) await using second = await tmpdir({ git: true }) @@ -40,6 +58,7 @@ describe("Session.listGlobal", () => { }) test("excludes archived sessions by default", async () => { + const { Session } = await import("../../src/session/index") await using tmp = await tmpdir({ git: true }) const archived = await Instance.provide({ @@ -64,6 +83,7 @@ describe("Session.listGlobal", () => { }) test("supports cursor pagination", async () => { + const { Session } = await import("../../src/session/index") await using tmp = await tmpdir({ git: true }) const first = await Instance.provide({ @@ -78,12 +98,46 @@ describe("Session.listGlobal", () => { const page = [...Session.listGlobal({ directory: tmp.path, limit: 1 })] expect(page.length).toBe(1) - expect(page[0].id).toBe(second.id) + expect(page[0]!.id).toBe(second.id) - const next = [...Session.listGlobal({ directory: tmp.path, limit: 10, cursor: page[0].time.updated })] + const next = [...Session.listGlobal({ directory: tmp.path, limit: 10, cursor: page[0]!.time.updated })] const ids = next.map((session) => session.id) expect(ids).toContain(first.id) expect(ids).not.toContain(second.id) }) + + test("filters by project ID across worktrees", async () => { + const { Session } = await import("../../src/session/index") + await using first = await tmpdir({ git: true }) + await using second = await tmpdir({ git: true }) + const worktree = path.join(first.path, "..", path.basename(first.path) + "-worktree") + + try { + await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + + const root = await Instance.provide({ + directory: first.path, + fn: async () => Session.create({ title: "root-session" }), + }) + const branch = await Instance.provide({ + directory: worktree, + fn: async () => Session.create({ title: "worktree-session" }), + }) + const other = await Instance.provide({ + directory: second.path, + fn: async () => Session.create({ title: "other-session" }), + }) + + const sessions = [...Session.listGlobal({ projectID: root.projectID, roots: true, limit: 200 })] + const ids = sessions.map((session) => session.id) + + expect(ids).toContain(root.id) + expect(ids).toContain(branch.id) + expect(ids).not.toContain(other.id) + expect(sessions.find((session) => session.id === branch.id)?.directory).toBe(worktree) + } finally { + await $`git worktree remove ${worktree}`.cwd(first.path).quiet().nothrow() + } + }) }) diff --git a/packages/opencode/test/tool/recall.test.ts b/packages/opencode/test/tool/recall.test.ts new file mode 100644 index 00000000000..3eb2055202a --- /dev/null +++ b/packages/opencode/test/tool/recall.test.ts @@ -0,0 +1,111 @@ +import { afterEach, describe, expect, mock, test } from "bun:test" +import { $ } from "bun" +import path from "path" +import { Instance } from "../../src/project/instance" +import { Config } from "../../src/config/config" +import { RecallTool } from "../../src/tool/recall" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" +import type { Tool } from "../../src/tool/tool" + +mock.module("@/kilo-sessions/remote-sender", () => ({ + RemoteSender: { + create() { + return { + handle() {}, + dispose() {}, + } + }, + }, +})) + +const ctx: Tool.Context = { + sessionID: "ses_test", + messageID: "msg_test", + callID: "call_test", + agent: "code", + abort: AbortSignal.any([]), + messages: [], + metadata: () => {}, + ask: async () => {}, +} + +afterEach(async () => { + await resetDatabase() +}) + +describe("tool.recall", () => { + test("search is limited to the current project worktrees", async () => { + await using first = await tmpdir({ git: true }) + await using second = await tmpdir({ git: true }) + const worktree = path.join(first.path, "..", path.basename(first.path) + "-worktree") + + try { + await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + + const share = Config.get + Config.get = async () => ({ share: "manual" }) as Awaited> + + try { + const { Session } = await import("../../src/session/index") + await Instance.provide({ + directory: first.path, + fn: async () => Session.create({ title: "search-target root" }), + }) + await Instance.provide({ + directory: worktree, + fn: async () => Session.create({ title: "search-target worktree" }), + }) + await Instance.provide({ + directory: second.path, + fn: async () => Session.create({ title: "search-target other" }), + }) + + const result = await Instance.provide({ + directory: first.path, + fn: async () => { + const tool = await RecallTool.init() + return tool.execute({ mode: "search", query: "search-target" }, ctx) + }, + }) + + expect(result.output).toContain("search-target root") + expect(result.output).toContain("search-target worktree") + expect(result.output).not.toContain("search-target other") + } finally { + Config.get = share + } + } finally { + await $`git worktree remove ${worktree}`.cwd(first.path).quiet().nothrow() + } + }) + + test("read rejects sessions from another project", async () => { + await using first = await tmpdir({ git: true }) + await using second = await tmpdir({ git: true }) + + const share = Config.get + Config.get = async () => ({ share: "manual" }) as Awaited> + + try { + const { Session } = await import("../../src/session/index") + const session = await Instance.provide({ + directory: second.path, + fn: async () => Session.create({ title: "other-project-session" }), + }) + + const err = await Instance.provide({ + directory: first.path, + fn: async () => { + const tool = await RecallTool.init() + return tool.execute({ mode: "read", sessionID: session.id }, ctx).catch((error) => error as Error) + }, + }) + + expect(err).toBeInstanceOf(Error) + expect((err as Error).message).toContain("belongs to a different project") + } finally { + Config.get = share + } + }) +}) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index ac486641cd4..b2c429b9fe0 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1065,6 +1065,7 @@ export class Session extends HeyApiClient { parameters?: { directory?: string workspace?: string + projectID?: string roots?: boolean start?: number cursor?: number @@ -1081,6 +1082,7 @@ export class Session extends HeyApiClient { args: [ { in: "query", key: "directory" }, { in: "query", key: "workspace" }, + { in: "query", key: "projectID" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "cursor" }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 8b90d809525..42e67654b98 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2843,6 +2843,10 @@ export type ExperimentalSessionListData = { */ directory?: string workspace?: string + /** + * Filter sessions by project ID + */ + projectID?: string /** * Only return root sessions (no parentID) */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 905852293d2..140ef2e919d 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1790,6 +1790,14 @@ "type": "string" } }, + { + "in": "query", + "name": "projectID", + "schema": { + "type": "string" + }, + "description": "Filter sessions by project ID" + }, { "in": "query", "name": "roots", From 663baf976383ffb4a5284b6476890e148ebad234 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 3 Apr 2026 22:50:11 +0300 Subject: [PATCH 16/33] fix(cli): remove worktree labels from session picker Keep /sessions and the Ctrl+A worktree view focused on session titles now that the picker is already scoped to project worktrees. --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index b06903569cf..5126ee8de6a 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,7 +2,6 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import path from "path" import { createMemo, createSignal, createResource, onMount } from "solid-js" import { Locale } from "@/util/locale" import { useKeybind } from "../context/keybind" @@ -70,10 +69,8 @@ export function DialogSessionList() { const isDeleting = toDelete() === x.id const status = sync.data.session_status?.[x.id] const isWorking = status?.type === "busy" - const root = all ? path.basename(x.directory) || x.directory : "" // kilocode_change - const suffix = root ? ` [${root}]` : "" return { - title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title + suffix, // kilocode_change + title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, bg: isDeleting ? theme.error : undefined, value: x.id, category, From 0c3c233b9227a88f6076570ae30ea877bfdf49fd Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Fri, 3 Apr 2026 23:54:09 +0300 Subject: [PATCH 17/33] fix: include sibling worktree sessions when project ids drift Resolve all-worktree session lookups by git worktree family instead of a single project row so /sessions, Ctrl+A, and local recall still find sibling worktrees without leaking unrelated directories. --- .../cmd/tui/component/dialog-session-list.tsx | 3 +- .../opencode/src/kilocode/worktree-family.ts | 35 + .../src/server/routes/experimental.ts | 13 +- packages/opencode/src/session/index.ts | 43 +- packages/opencode/src/tool/recall.ts | 13 +- .../server/experimental-session-list.test.ts | 7 +- .../test/server/global-session-list.test.ts | 4 +- packages/opencode/test/tool/recall.test.ts | 38 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 4 + packages/sdk/openapi.json | 2109 +++-------------- 11 files changed, 537 insertions(+), 1734 deletions(-) create mode 100644 packages/opencode/src/kilocode/worktree-family.ts diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 5126ee8de6a..c37c737ab3c 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -29,12 +29,11 @@ export function DialogSessionList() { async ({ query, global: all }) => { if (!query && !all) return undefined if (all) { - const project = await sdk.client.project.current({}, { throwOnError: true }) const result = await sdk.client.experimental.session.list( { - projectID: project.data?.id, search: query || undefined, roots: true, + worktrees: true, limit: 30, }, { throwOnError: true }, diff --git a/packages/opencode/src/kilocode/worktree-family.ts b/packages/opencode/src/kilocode/worktree-family.ts new file mode 100644 index 00000000000..0248c171410 --- /dev/null +++ b/packages/opencode/src/kilocode/worktree-family.ts @@ -0,0 +1,35 @@ +// kilocode_change - new file +import { Instance } from "../project/instance" +import { Project } from "../project/project" +import { Filesystem } from "../util/filesystem" +import { git } from "../util/git" + +export namespace WorktreeFamily { + export async function list() { + if (Instance.project.vcs !== "git") { + return [Filesystem.resolve(Instance.directory)] + } + + const listed = await git(["worktree", "list", "--porcelain"], { + cwd: Instance.worktree, + }) + + if (listed.exitCode === 0) { + const dirs = listed + .text() + .split("\n") + .map((line) => line.trim()) + .flatMap((line) => { + if (!line.startsWith("worktree ")) return [] + return [Filesystem.resolve(line.slice("worktree ".length).trim())] + }) + + if (dirs.length > 0) { + return [...new Set(dirs)] + } + } + + const dirs = [Instance.worktree, ...(await Project.sandboxes(Instance.project.id))] + return [...new Set(dirs.map((dir) => Filesystem.resolve(dir)))] + } +} diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index a3672d7b694..1acd7be0039 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -13,6 +13,7 @@ import { lazy } from "../../util/lazy" import { Snapshot } from "../../snapshot" // kilocode_change import { Review } from "../../kilocode/review/review" // kilocode_change import { WorktreeDiff } from "../../kilocode/review/worktree-diff" // kilocode_change +import { WorktreeFamily } from "../../kilocode/worktree-family" // kilocode_change import { Log } from "../../util/log" // kilocode_change import { WorkspaceRoutes } from "./workspace" @@ -329,6 +330,10 @@ export const ExperimentalRoutes = lazy(() => // kilocode_change projectID: z.string().optional().meta({ description: "Filter sessions by project ID" }), directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), + worktrees: z.coerce + .boolean() + .optional() + .meta({ description: "Restrict sessions to the current repo worktree family or current directory" }), roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), start: z.coerce .number() @@ -346,10 +351,16 @@ export const ExperimentalRoutes = lazy(() => async (c) => { const query = c.req.valid("query") const limit = query.limit ?? 100 + const projectID = + query.worktrees && !query.projectID + ? Instance.project.id // kilocode_change + : query.projectID + const directories = query.worktrees ? await WorktreeFamily.list() : undefined // kilocode_change const sessions: Session.GlobalInfo[] = [] for await (const session of Session.listGlobal({ - projectID: query.projectID, // kilocode_change + projectID, // kilocode_change directory: query.directory, + directories, // kilocode_change roots: query.roots, start: query.start, cursor: query.cursor, diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 504c6f5719f..4eca075c919 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -117,6 +117,25 @@ export namespace Session { return `${title} (fork #1)` } + // kilocode_change start + function family(id: string) { + const row = Database.use((db) => + db.select({ worktree: ProjectTable.worktree }).from(ProjectTable).where(eq(ProjectTable.id, id)).get(), + ) + const root = row?.worktree ? Filesystem.resolve(row.worktree) : undefined + if (!root || root === "/") return [id] + const ids = Database.use((db) => + db + .select({ id: ProjectTable.id }) + .from(ProjectTable) + .where(eq(ProjectTable.worktree, root)) + .all() + .map((item) => item.id), + ) + return ids.length ? ids : [id] + } + // kilocode_change end + export const Info = z .object({ id: Identifier.schema("session"), @@ -624,6 +643,7 @@ export namespace Session { export function* listGlobal(input?: { projectID?: string directory?: string + directories?: string[] roots?: boolean start?: number cursor?: number @@ -635,7 +655,12 @@ export namespace Session { // kilocode_change start if (input?.projectID) { - conditions.push(eq(SessionTable.project_id, input.projectID)) + const ids = family(input.projectID) + if (ids.length === 1 && ids[0] === input.projectID) { + conditions.push(eq(SessionTable.project_id, input.projectID)) + } else { + conditions.push(inArray(SessionTable.project_id, ids)) + } } // kilocode_change end @@ -661,6 +686,7 @@ export namespace Session { } const limit = input?.limit ?? 100 + const dirs = [...new Set((input?.directories ?? []).map((dir) => Filesystem.resolve(dir)))] const rows = Database.use((db) => { const query = @@ -670,10 +696,19 @@ export namespace Session { .from(SessionTable) .where(and(...conditions)) : db.select().from(SessionTable) - return query.orderBy(desc(SessionTable.time_updated), desc(SessionTable.id)).limit(limit).all() + const sorted = query.orderBy(desc(SessionTable.time_updated), desc(SessionTable.id)) + return dirs.length ? sorted.all() : sorted.limit(limit).all() }) - const ids = [...new Set(rows.map((row) => row.project_id))] + const list = + dirs.length > 0 + ? rows.filter((row) => { + const dir = Filesystem.resolve(row.directory) + return dirs.some((root) => Filesystem.contains(root, dir)) + }) + : rows + + const ids = [...new Set(list.slice(0, limit).map((row) => row.project_id))] const projects = new Map() if (ids.length > 0) { @@ -693,7 +728,7 @@ export namespace Session { } } - for (const row of rows) { + for (const row of list.slice(0, limit)) { const project = projects.get(row.project_id) ?? null yield { ...fromRow(row), project } } diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index 502603467fa..3bf20e54376 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -3,6 +3,8 @@ import z from "zod" import { Tool } from "./tool" import { Instance } from "../project/instance" import { Locale } from "../util/locale" +import { Filesystem } from "../util/filesystem" // kilocode_change +import { WorktreeFamily } from "../kilocode/worktree-family" // kilocode_change import DESCRIPTION from "./recall.txt" export const RecallTool = Tool.define("kilo_local_recall", { @@ -37,7 +39,7 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont }) const limit = Math.min(params.limit ?? 20, 50) - const current = Instance.project.id + const dirs = await WorktreeFamily.list() // kilocode_change const { Session } = await import("../session/index") // kilocode_change const results: Array<{ @@ -48,7 +50,8 @@ async function search(params: { query?: string; limit?: number }, ctx: Tool.Cont }> = [] for (const session of Session.listGlobal({ - projectID: current, // kilocode_change + projectID: Instance.project.id, // kilocode_change + directories: dirs, // kilocode_change search: params.query, roots: true, limit, @@ -87,10 +90,12 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { const session = await Session.get(params.sessionID).catch(() => { throw new Error(`Session "${params.sessionID}" not found. Use search mode first to find valid session IDs.`) }) + const dirs = await WorktreeFamily.list() // kilocode_change // kilocode_change start - if (session.projectID !== Instance.project.id) { + const dir = Filesystem.resolve(session.directory) + if (!dirs.some((root) => Filesystem.contains(root, dir))) { throw new Error( - `Session "${params.sessionID}" belongs to a different project and cannot be read from this workspace.`, + `Session "${params.sessionID}" belongs to a different workspace and cannot be read from this directory.`, ) } // kilocode_change end diff --git a/packages/opencode/test/server/experimental-session-list.test.ts b/packages/opencode/test/server/experimental-session-list.test.ts index db8dcde9403..2f93482725c 100644 --- a/packages/opencode/test/server/experimental-session-list.test.ts +++ b/packages/opencode/test/server/experimental-session-list.test.ts @@ -25,13 +25,14 @@ afterEach(async () => { }) describe("experimental.session.list", () => { - test("filters sessions by project ID across worktrees", async () => { + test("filters sessions by repo worktree family even when project IDs drift", async () => { await using first = await tmpdir({ git: true }) await using second = await tmpdir({ git: true }) const worktree = path.join(first.path, "..", path.basename(first.path) + "-worktree") try { await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") const share = Config.get Config.get = async () => ({ share: "manual" }) as Awaited> @@ -63,7 +64,7 @@ describe("experimental.session.list", () => { const app = root.app const project = await root.project.json() const response = await app.request( - `/experimental/session?projectID=${encodeURIComponent(project.id)}&roots=true`, + `/experimental/session?projectID=${encodeURIComponent(project.id)}&roots=true&worktrees=true`, { headers: { "x-kilo-directory": first.path }, }, @@ -74,6 +75,8 @@ describe("experimental.session.list", () => { const ids = body.map((item: { id: string }) => item.id) const dirs = body.map((item: { directory: string }) => item.directory) + expect(root.session.projectID).not.toBe(branch.projectID) + expect(project.id).toBe(root.session.projectID) expect(ids).toContain(root.session.id) expect(ids).toContain(branch.id) expect(dirs).toContain(worktree) diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 8eb20ea549c..34fa3cea15c 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -107,7 +107,7 @@ describe("Session.listGlobal", () => { expect(ids).not.toContain(second.id) }) - test("filters by project ID across worktrees", async () => { + test("filters by project family across worktrees when project IDs drift", async () => { const { Session } = await import("../../src/session/index") await using first = await tmpdir({ git: true }) await using second = await tmpdir({ git: true }) @@ -115,6 +115,7 @@ describe("Session.listGlobal", () => { try { await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") const root = await Instance.provide({ directory: first.path, @@ -132,6 +133,7 @@ describe("Session.listGlobal", () => { const sessions = [...Session.listGlobal({ projectID: root.projectID, roots: true, limit: 200 })] const ids = sessions.map((session) => session.id) + expect(root.projectID).not.toBe(branch.projectID) expect(ids).toContain(root.id) expect(ids).toContain(branch.id) expect(ids).not.toContain(other.id) diff --git a/packages/opencode/test/tool/recall.test.ts b/packages/opencode/test/tool/recall.test.ts index 3eb2055202a..3275a6e98b7 100644 --- a/packages/opencode/test/tool/recall.test.ts +++ b/packages/opencode/test/tool/recall.test.ts @@ -42,6 +42,7 @@ describe("tool.recall", () => { try { await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") const share = Config.get Config.get = async () => ({ share: "manual" }) as Awaited> @@ -103,9 +104,44 @@ describe("tool.recall", () => { }) expect(err).toBeInstanceOf(Error) - expect((err as Error).message).toContain("belongs to a different project") + expect((err as Error).message).toContain("belongs to a different workspace") } finally { Config.get = share } }) + + test("read allows sessions from sibling worktrees when project IDs drift", async () => { + await using first = await tmpdir({ git: true }) + const worktree = path.join(first.path, "..", path.basename(first.path) + "-worktree") + + try { + await $`git worktree add ${worktree} -b test-branch-${Date.now()}`.cwd(first.path).quiet() + await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") + + const share = Config.get + Config.get = async () => ({ share: "manual" }) as Awaited> + + try { + const { Session } = await import("../../src/session/index") + const session = await Instance.provide({ + directory: worktree, + fn: async () => Session.create({ title: "worktree readable" }), + }) + + const result = await Instance.provide({ + directory: first.path, + fn: async () => { + const tool = await RecallTool.init() + return tool.execute({ mode: "read", sessionID: session.id }, ctx) + }, + }) + + expect(result.output).toContain("# Session: worktree readable") + } finally { + Config.get = share + } + } finally { + await $`git worktree remove ${worktree}`.cwd(first.path).quiet().nothrow() + } + }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index b2c429b9fe0..99f4020b7c1 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1066,6 +1066,7 @@ export class Session extends HeyApiClient { directory?: string workspace?: string projectID?: string + worktrees?: boolean roots?: boolean start?: number cursor?: number @@ -1083,6 +1084,7 @@ export class Session extends HeyApiClient { { in: "query", key: "directory" }, { in: "query", key: "workspace" }, { in: "query", key: "projectID" }, + { in: "query", key: "worktrees" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "cursor" }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 42e67654b98..9d8ebdb980d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2847,6 +2847,10 @@ export type ExperimentalSessionListData = { * Filter sessions by project ID */ projectID?: string + /** + * Restrict sessions to the current repo worktree family or current directory + */ + worktrees?: boolean /** * Only return root sessions (no parentID) */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 140ef2e919d..a2e81ed9b52 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -27,10 +27,7 @@ "type": "string" } }, - "required": [ - "healthy", - "version" - ] + "required": ["healthy", "version"] } } } @@ -745,10 +742,7 @@ "type": "number" } }, - "required": [ - "rows", - "cols" - ] + "required": ["rows", "cols"] } } } @@ -1023,10 +1017,7 @@ } } }, - "required": [ - "providers", - "default" - ] + "required": ["providers", "default"] } } } @@ -1233,11 +1224,7 @@ ] } }, - "required": [ - "type", - "branch", - "extra" - ] + "required": ["type", "branch", "extra"] } } } @@ -1798,6 +1785,14 @@ }, "description": "Filter sessions by project ID" }, + { + "in": "query", + "name": "worktrees", + "schema": { + "type": "boolean" + }, + "description": "Restrict sessions to the current repo worktree family or current directory" + }, { "in": "query", "name": "roots", @@ -2156,9 +2151,7 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific Kilo session.", - "tags": [ - "Session" - ], + "tags": ["Session"], "responses": { "200": { "description": "Get session", @@ -2385,9 +2378,7 @@ } ], "summary": "Get session children", - "tags": [ - "Session" - ], + "tags": ["Session"], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -2584,11 +2575,7 @@ "pattern": "^msg.*" } }, - "required": [ - "modelID", - "providerID", - "messageID" - ] + "required": ["modelID", "providerID", "messageID"] } } } @@ -3012,10 +2999,7 @@ "type": "boolean" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] } } } @@ -3085,10 +3069,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3169,10 +3150,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3218,10 +3196,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3291,9 +3266,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3363,10 +3336,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3733,10 +3703,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3806,9 +3773,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3869,10 +3834,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3950,20 +3912,13 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] } ] } } }, - "required": [ - "arguments", - "command" - ] + "required": ["arguments", "command"] } } } @@ -4057,19 +4012,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "agent", - "command" - ] + "required": ["agent", "command"] } } } @@ -4158,9 +4107,7 @@ "pattern": "^prt.*" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } } } @@ -4320,16 +4267,10 @@ "properties": { "response": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "response" - ] + "required": ["response"] } } } @@ -4466,19 +4407,13 @@ "properties": { "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] }, "message": { "type": "string" } }, - "required": [ - "reply" - ] + "required": ["reply"] } } } @@ -4746,9 +4681,7 @@ } } }, - "required": [ - "answers" - ] + "required": ["answers"] } } } @@ -4925,15 +4858,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -4969,16 +4897,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -4993,10 +4915,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -5005,70 +4924,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -5125,12 +5013,7 @@ } } }, - "required": [ - "name", - "env", - "id", - "models" - ] + "required": ["name", "env", "id", "models"] } }, "default": { @@ -5149,11 +5032,7 @@ } } }, - "required": [ - "all", - "default", - "connected" - ] + "required": ["all", "default", "connected"] } } } @@ -5280,9 +5159,7 @@ "type": "number" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5362,9 +5239,7 @@ "type": "string" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5439,9 +5314,7 @@ "additionalProperties": {} } }, - "required": [ - "event" - ] + "required": ["event"] } } } @@ -5490,10 +5363,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5543,10 +5413,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5596,10 +5463,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5646,9 +5510,7 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -5686,9 +5548,7 @@ "type": "string" } }, - "required": [ - "path" - ] + "required": ["path"] } } } @@ -5734,9 +5594,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5764,9 +5622,7 @@ "minLength": 1 } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5818,10 +5674,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -5885,13 +5738,7 @@ } } }, - "required": [ - "id", - "worktree", - "timeCreated", - "timeUpdated", - "sandboxes" - ] + "required": ["id", "worktree", "timeCreated", "timeUpdated", "sandboxes"] } } } @@ -5943,10 +5790,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -6018,11 +5862,7 @@ } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "revert": { "type": "object", @@ -6040,9 +5880,7 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] }, "permission": { "type": "object", @@ -6064,16 +5902,7 @@ "type": "number" } }, - "required": [ - "id", - "projectID", - "slug", - "directory", - "title", - "version", - "timeCreated", - "timeUpdated" - ] + "required": ["id", "projectID", "slug", "directory", "title", "version", "timeCreated", "timeUpdated"] } } } @@ -6125,10 +5954,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -6175,9 +6001,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "agent": { "type": "string" @@ -6192,10 +6016,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "tools": { "type": "object", @@ -6207,12 +6028,7 @@ } } }, - "required": [ - "role", - "time", - "agent", - "model" - ] + "required": ["role", "time", "agent", "model"] }, { "type": "object", @@ -6231,9 +6047,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "parentID": { "type": "string" @@ -6260,10 +6074,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ] + "required": ["cwd", "root"] }, "summary": { "type": "boolean" @@ -6296,18 +6107,10 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] }, "structured": {}, "variant": { @@ -6333,12 +6136,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "timeCreated", - "data" - ] + "required": ["id", "sessionID", "timeCreated", "data"] } } } @@ -6390,10 +6188,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -6455,9 +6250,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -6467,10 +6260,7 @@ "additionalProperties": {} } }, - "required": [ - "type", - "text" - ] + "required": ["type", "text"] }, { "type": "object", @@ -6499,16 +6289,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "type", - "text", - "time" - ] + "required": ["type", "text", "time"] }, { "type": "object", @@ -6543,11 +6327,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ] + "required": ["status", "input", "raw"] }, { "type": "object", @@ -6580,16 +6360,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "status", - "input", - "time" - ] + "required": ["status", "input", "time"] }, { "type": "object", @@ -6631,20 +6405,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ] + "required": ["status", "input", "output", "title", "metadata", "time"] }, { "type": "object", @@ -6680,18 +6444,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "error", - "time" - ] + "required": ["status", "input", "error", "time"] } ] }, @@ -6703,22 +6459,12 @@ "additionalProperties": {} } }, - "required": [ - "type", - "callID", - "tool", - "state" - ] + "required": ["type", "callID", "tool", "state"] } ] } }, - "required": [ - "id", - "messageID", - "sessionID", - "data" - ] + "required": ["id", "messageID", "sessionID", "data"] } } } @@ -6784,9 +6530,7 @@ "type": "string" } }, - "required": [ - "location" - ] + "required": ["location"] } } } @@ -6852,9 +6596,7 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } } } @@ -6920,17 +6662,11 @@ "type": "string" } }, - "required": [ - "id", - "name", - "role" - ] + "required": ["id", "name", "role"] } } }, - "required": [ - "email" - ] + "required": ["email"] }, "balance": { "anyOf": [ @@ -6941,9 +6677,7 @@ "type": "number" } }, - "required": [ - "balance" - ] + "required": ["balance"] }, { "type": "null" @@ -6961,11 +6695,7 @@ ] } }, - "required": [ - "profile", - "balance", - "currentOrgId" - ] + "required": ["profile", "balance", "currentOrgId"] } } } @@ -7049,9 +6779,7 @@ ] } }, - "required": [ - "organizationId" - ] + "required": ["organizationId"] } } } @@ -7179,9 +6907,7 @@ } } }, - "required": [ - "modes" - ] + "required": ["modes"] } } } @@ -7292,10 +7018,7 @@ "type": "number" } }, - "required": [ - "prefix", - "suffix" - ] + "required": ["prefix", "suffix"] } } } @@ -7358,10 +7081,7 @@ "type": "string" } }, - "required": [ - "actionText", - "actionURL" - ] + "required": ["actionText", "actionURL"] }, "showIn": { "type": "array", @@ -7373,11 +7093,7 @@ "type": "string" } }, - "required": [ - "id", - "title", - "message" - ] + "required": ["id", "title", "message"] } } } @@ -7520,9 +7236,7 @@ "type": "string" } }, - "required": [ - "sessionId" - ] + "required": ["sessionId"] } } } @@ -7568,14 +7282,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "provisioned", - "starting", - "restarting", - "running", - "stopped", - "destroying" - ] + "enum": ["provisioned", "starting", "restarting", "running", "stopped", "destroying"] }, { "type": "null" @@ -7598,10 +7305,7 @@ "type": "number" } }, - "required": [ - "cpus", - "memory_mb" - ] + "required": ["cpus", "memory_mb"] }, "openclawVersion": { "anyOf": [ @@ -7643,9 +7347,7 @@ "type": "string" } }, - "required": [ - "status" - ] + "required": ["status"] } } } @@ -7703,12 +7405,7 @@ "type": "string" } }, - "required": [ - "apiKey", - "userId", - "userToken", - "channelId" - ] + "required": ["apiKey", "userId", "userToken", "channelId"] }, { "type": "null" @@ -7805,13 +7502,7 @@ "type": "number" } }, - "required": [ - "session_id", - "title", - "created_at", - "updated_at", - "version" - ] + "required": ["session_id", "title", "created_at", "updated_at", "version"] } }, "nextCursor": { @@ -7825,10 +7516,7 @@ ] } }, - "required": [ - "cliSessions", - "nextCursor" - ] + "required": ["cliSessions", "nextCursor"] } } } @@ -7898,9 +7586,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "lines": { "type": "object", @@ -7909,9 +7595,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "line_number": { "type": "number" @@ -7931,9 +7615,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "start": { "type": "number" @@ -7942,21 +7624,11 @@ "type": "number" } }, - "required": [ - "match", - "start", - "end" - ] + "required": ["match", "start", "end"] } } }, - "required": [ - "path", - "lines", - "line_number", - "absolute_offset", - "submatches" - ] + "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] } } } @@ -8002,10 +7674,7 @@ "name": "dirs", "schema": { "type": "string", - "enum": [ - "true", - "false" - ] + "enum": ["true", "false"] } }, { @@ -8013,10 +7682,7 @@ "name": "type", "schema": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] } }, { @@ -8365,10 +8031,7 @@ ] } }, - "required": [ - "name", - "config" - ] + "required": ["name", "config"] } } } @@ -8423,9 +8086,7 @@ "type": "string" } }, - "required": [ - "authorizationUrl" - ] + "required": ["authorizationUrl"] } } } @@ -8499,9 +8160,7 @@ "const": true } }, - "required": [ - "success" - ] + "required": ["success"] } } } @@ -8597,9 +8256,7 @@ "type": "string" } }, - "required": [ - "code" - ] + "required": ["code"] } } } @@ -8830,9 +8487,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -9144,9 +8799,7 @@ "type": "string" } }, - "required": [ - "command" - ] + "required": ["command"] } } } @@ -9206,12 +8859,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -9219,10 +8867,7 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } } } @@ -9373,9 +9018,7 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } } } @@ -9422,10 +9065,7 @@ }, "body": {} }, - "required": [ - "path", - "body" - ] + "required": ["path", "body"] } } } @@ -9710,12 +9350,7 @@ "level": { "description": "Log level", "type": "string", - "enum": [ - "debug", - "info", - "error", - "warn" - ] + "enum": ["debug", "info", "error", "warn"] }, "message": { "description": "Log message", @@ -9730,11 +9365,7 @@ "additionalProperties": {} } }, - "required": [ - "service", - "level", - "message" - ] + "required": ["service", "level", "message"] } } } @@ -9835,12 +9466,7 @@ "type": "string" } }, - "required": [ - "name", - "description", - "location", - "content" - ] + "required": ["name", "description", "location", "content"] } } } @@ -10001,15 +9627,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.installation.update-available": { "type": "object", @@ -10025,15 +9646,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Project": { "type": "object", @@ -10087,10 +9703,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "sandboxes": { "type": "array", @@ -10099,12 +9712,7 @@ } } }, - "required": [ - "id", - "worktree", - "time", - "sandboxes" - ] + "required": ["id", "worktree", "time", "sandboxes"] }, "Event.project.updated": { "type": "object", @@ -10117,10 +9725,7 @@ "$ref": "#/components/schemas/Project" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.instance.disposed": { "type": "object", @@ -10136,15 +9741,10 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.connected": { "type": "object", @@ -10158,10 +9758,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.disposed": { "type": "object", @@ -10175,10 +9772,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.config.updated": { "type": "object", @@ -10192,10 +9786,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -10214,16 +9805,10 @@ "type": "string" } }, - "required": [ - "serverID", - "path" - ] + "required": ["serverID", "path"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.updated": { "type": "object", @@ -10237,10 +9822,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.edited": { "type": "object", @@ -10256,15 +9838,10 @@ "type": "string" } }, - "required": [ - "file" - ] + "required": ["file"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "OutputFormatText": { "type": "object", @@ -10274,9 +9851,7 @@ "const": "text" } }, - "required": [ - "type" - ] + "required": ["type"] }, "JSONSchema": { "type": "object", @@ -10302,10 +9877,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "schema" - ] + "required": ["type", "schema"] }, "OutputFormat": { "anyOf": [ @@ -10337,20 +9909,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "before", - "after", - "additions", - "deletions" - ] + "required": ["file", "before", "after", "additions", "deletions"] }, "UserMessage": { "type": "object", @@ -10372,9 +9934,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "format": { "$ref": "#/components/schemas/OutputFormat" @@ -10395,9 +9955,7 @@ } } }, - "required": [ - "diffs" - ] + "required": ["diffs"] }, "agent": { "type": "string" @@ -10412,10 +9970,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "system": { "type": "string" @@ -10456,14 +10011,7 @@ } } }, - "required": [ - "id", - "sessionID", - "role", - "time", - "agent", - "model" - ] + "required": ["id", "sessionID", "role", "time", "agent", "model"] }, "ProviderAuthError": { "type": "object", @@ -10482,16 +10030,10 @@ "type": "string" } }, - "required": [ - "providerID", - "message" - ] + "required": ["providerID", "message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "UnknownError": { "type": "object", @@ -10507,15 +10049,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageOutputLengthError": { "type": "object", @@ -10529,10 +10066,7 @@ "properties": {} } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageAbortedError": { "type": "object", @@ -10548,15 +10082,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "StructuredOutputError": { "type": "object", @@ -10575,16 +10104,10 @@ "type": "number" } }, - "required": [ - "message", - "retries" - ] + "required": ["message", "retries"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "ContextOverflowError": { "type": "object", @@ -10603,15 +10126,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "APIError": { "type": "object", @@ -10654,16 +10172,10 @@ } } }, - "required": [ - "message", - "isRetryable" - ] + "required": ["message", "isRetryable"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "AssistantMessage": { "type": "object", @@ -10688,9 +10200,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "error": { "anyOf": [ @@ -10742,10 +10252,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ] + "required": ["cwd", "root"] }, "summary": { "type": "boolean" @@ -10778,18 +10285,10 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] }, "structured": {}, "variant": { @@ -10838,15 +10337,10 @@ "$ref": "#/components/schemas/Message" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.removed": { "type": "object", @@ -10865,16 +10359,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID" - ] + "required": ["sessionID", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "TextPart": { "type": "object", @@ -10911,9 +10399,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -10923,13 +10409,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text" - ] + "required": ["id", "sessionID", "messageID", "type", "text"] }, "SubtaskPart": { "type": "object", @@ -10966,24 +10446,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "prompt", - "description", - "agent" - ] + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] }, "ReasoningPart": { "type": "object", @@ -11021,19 +10490,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "text", "time"] }, "FilePartSourceText": { "type": "object", @@ -11052,11 +10512,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] }, "FileSource": { "type": "object", @@ -11072,11 +10528,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "path" - ] + "required": ["text", "type", "path"] }, "Range": { "type": "object", @@ -11091,10 +10543,7 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] }, "end": { "type": "object", @@ -11106,16 +10555,10 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "SymbolSource": { "type": "object", @@ -11142,14 +10585,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "text", - "type", - "path", - "range", - "name", - "kind" - ] + "required": ["text", "type", "path", "range", "name", "kind"] }, "ResourceSource": { "type": "object", @@ -11168,12 +10604,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "clientName", - "uri" - ] + "required": ["text", "type", "clientName", "uri"] }, "FilePartSource": { "anyOf": [ @@ -11217,14 +10648,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "mime", - "url" - ] + "required": ["id", "sessionID", "messageID", "type", "mime", "url"] }, "ToolStatePending": { "type": "object", @@ -11244,11 +10668,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ] + "required": ["status", "input", "raw"] }, "ToolStateRunning": { "type": "object", @@ -11281,16 +10701,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "status", - "input", - "time" - ] + "required": ["status", "input", "time"] }, "ToolStateCompleted": { "type": "object", @@ -11332,10 +10746,7 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "attachments": { "type": "array", @@ -11344,14 +10755,7 @@ } } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ] + "required": ["status", "input", "output", "title", "metadata", "time"] }, "ToolStateError": { "type": "object", @@ -11387,18 +10791,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "error", - "time" - ] + "required": ["status", "input", "error", "time"] }, "ToolState": { "anyOf": [ @@ -11449,15 +10845,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "callID", - "tool", - "state" - ] + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] }, "StepStartPart": { "type": "object", @@ -11479,12 +10867,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type" - ] + "required": ["id", "sessionID", "messageID", "type"] }, "StepFinishPart": { "type": "object", @@ -11536,29 +10919,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "reason", - "cost", - "tokens" - ] + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] }, "SnapshotPart": { "type": "object", @@ -11580,13 +10947,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "snapshot" - ] + "required": ["id", "sessionID", "messageID", "type", "snapshot"] }, "PatchPart": { "type": "object", @@ -11614,14 +10975,7 @@ } } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "hash", - "files" - ] + "required": ["id", "sessionID", "messageID", "type", "hash", "files"] }, "AgentPart": { "type": "object", @@ -11659,20 +11013,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "name" - ] + "required": ["id", "sessionID", "messageID", "type", "name"] }, "RetryPart": { "type": "object", @@ -11703,20 +11047,10 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "attempt", - "error", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] }, "CompactionPart": { "type": "object", @@ -11741,13 +11075,7 @@ "type": "boolean" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "auto" - ] + "required": ["id", "sessionID", "messageID", "type", "auto"] }, "Part": { "anyOf": [ @@ -11803,15 +11131,10 @@ "$ref": "#/components/schemas/Part" } }, - "required": [ - "part" - ] + "required": ["part"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.delta": { "type": "object", @@ -11839,19 +11162,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID", - "field", - "delta" - ] + "required": ["sessionID", "messageID", "partID", "field", "delta"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.removed": { "type": "object", @@ -11873,17 +11187,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID" - ] + "required": ["sessionID", "messageID", "partID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionRequest": { "type": "object", @@ -11928,20 +11235,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "permission", - "patterns", - "metadata", - "always" - ] + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] }, "Event.permission.asked": { "type": "object", @@ -11954,10 +11251,7 @@ "$ref": "#/components/schemas/PermissionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.permission.replied": { "type": "object", @@ -11977,24 +11271,13 @@ }, "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "sessionID", - "requestID", - "reply" - ] + "required": ["sessionID", "requestID", "reply"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "SessionStatus": { "anyOf": [ @@ -12006,9 +11289,7 @@ "const": "idle" } }, - "required": [ - "type" - ] + "required": ["type"] }, { "type": "object", @@ -12027,12 +11308,7 @@ "type": "number" } }, - "required": [ - "type", - "attempt", - "message", - "next" - ] + "required": ["type", "attempt", "message", "next"] }, { "type": "object", @@ -12042,9 +11318,7 @@ "const": "busy" } }, - "required": [ - "type" - ] + "required": ["type"] } ] }, @@ -12065,16 +11339,10 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": [ - "sessionID", - "status" - ] + "required": ["sessionID", "status"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.idle": { "type": "object", @@ -12090,15 +11358,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionOption": { "type": "object", @@ -12116,10 +11379,7 @@ "type": "string" } }, - "required": [ - "label", - "description" - ] + "required": ["label", "description"] }, "QuestionInfo": { "type": "object", @@ -12148,11 +11408,7 @@ "type": "boolean" } }, - "required": [ - "question", - "header", - "options" - ] + "required": ["question", "header", "options"] }, "QuestionRequest": { "type": "object", @@ -12182,17 +11438,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "questions" - ] + "required": ["id", "sessionID", "questions"] }, "Event.question.asked": { "type": "object", @@ -12205,10 +11454,7 @@ "$ref": "#/components/schemas/QuestionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionAnswer": { "type": "array", @@ -12239,17 +11485,10 @@ } } }, - "required": [ - "sessionID", - "requestID", - "answers" - ] + "required": ["sessionID", "requestID", "answers"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.question.rejected": { "type": "object", @@ -12268,16 +11507,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "requestID" - ] + "required": ["sessionID", "requestID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.compacted": { "type": "object", @@ -12293,15 +11526,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.watcher.updated": { "type": "object", @@ -12333,16 +11561,10 @@ ] } }, - "required": [ - "file", - "event" - ] + "required": ["file", "event"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Todo": { "type": "object", @@ -12360,11 +11582,7 @@ "type": "string" } }, - "required": [ - "content", - "status", - "priority" - ] + "required": ["content", "status", "priority"] }, "Event.todo.updated": { "type": "object", @@ -12386,16 +11604,10 @@ } } }, - "required": [ - "sessionID", - "todos" - ] + "required": ["sessionID", "todos"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.prompt.append": { "type": "object", @@ -12411,15 +11623,10 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.command.execute": { "type": "object", @@ -12460,15 +11667,10 @@ ] } }, - "required": [ - "command" - ] + "required": ["command"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.toast.show": { "type": "object", @@ -12488,12 +11690,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -12501,16 +11698,10 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.session.select": { "type": "object", @@ -12528,15 +11719,10 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.tools.changed": { "type": "object", @@ -12552,15 +11738,10 @@ "type": "string" } }, - "required": [ - "server" - ] + "required": ["server"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.browser.open.failed": { "type": "object", @@ -12579,16 +11760,10 @@ "type": "string" } }, - "required": [ - "mcpName", - "url" - ] + "required": ["mcpName", "url"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.command.executed": { "type": "object", @@ -12615,26 +11790,14 @@ "pattern": "^msg.*" } }, - "required": [ - "name", - "sessionID", - "arguments", - "messageID" - ] + "required": ["name", "sessionID", "arguments", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionAction": { "type": "string", - "enum": [ - "allow", - "deny", - "ask" - ] + "enum": ["allow", "deny", "ask"] }, "PermissionRule": { "type": "object", @@ -12649,11 +11812,7 @@ "$ref": "#/components/schemas/PermissionAction" } }, - "required": [ - "permission", - "pattern", - "action" - ] + "required": ["permission", "pattern", "action"] }, "PermissionRuleset": { "type": "array", @@ -12712,26 +11871,14 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "additions", - "deletions" - ] + "required": ["file", "additions", "deletions"] } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -12740,9 +11887,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -12766,10 +11911,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -12790,20 +11932,10 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time"] }, "Event.session.created": { "type": "object", @@ -12819,15 +11951,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.updated": { "type": "object", @@ -12843,15 +11970,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.deleted": { "type": "object", @@ -12867,15 +11989,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.diff": { "type": "object", @@ -12897,16 +12014,10 @@ } } }, - "required": [ - "sessionID", - "diff" - ] + "required": ["sessionID", "diff"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.error": { "type": "object", @@ -12949,10 +12060,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.open": { "type": "object", @@ -12968,15 +12076,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.close": { "type": "object", @@ -12993,23 +12096,13 @@ }, "reason": { "type": "string", - "enum": [ - "completed", - "error", - "interrupted" - ] + "enum": ["completed", "error", "interrupted"] } }, - "required": [ - "sessionID", - "reason" - ] + "required": ["sessionID", "reason"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.vcs.branch.updated": { "type": "object", @@ -13027,10 +12120,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.ready": { "type": "object", @@ -13046,15 +12136,10 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.failed": { "type": "object", @@ -13070,15 +12155,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Pty": { "type": "object", @@ -13104,24 +12184,13 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited" - ] + "enum": ["running", "exited"] }, "pid": { "type": "number" } }, - "required": [ - "id", - "title", - "command", - "args", - "cwd", - "status", - "pid" - ] + "required": ["id", "title", "command", "args", "cwd", "status", "pid"] }, "Event.pty.created": { "type": "object", @@ -13137,15 +12206,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.updated": { "type": "object", @@ -13161,15 +12225,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.exited": { "type": "object", @@ -13189,16 +12248,10 @@ "type": "number" } }, - "required": [ - "id", - "exitCode" - ] + "required": ["id", "exitCode"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.deleted": { "type": "object", @@ -13215,15 +12268,10 @@ "pattern": "^pty.*" } }, - "required": [ - "id" - ] + "required": ["id"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.ready": { "type": "object", @@ -13242,16 +12290,10 @@ "type": "string" } }, - "required": [ - "name", - "branch" - ] + "required": ["name", "branch"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.failed": { "type": "object", @@ -13267,15 +12309,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event": { "anyOf": [ @@ -13435,20 +12472,12 @@ "$ref": "#/components/schemas/Event" } }, - "required": [ - "directory", - "payload" - ] + "required": ["directory", "payload"] }, "LogLevel": { "description": "Log level", "type": "string", - "enum": [ - "DEBUG", - "INFO", - "WARN", - "ERROR" - ] + "enum": ["DEBUG", "INFO", "WARN", "ERROR"] }, "ServerConfig": { "description": "Server configuration for kilo serve and web commands", @@ -13486,11 +12515,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] + "enum": ["ask", "allow", "deny"] }, { "type": "null" @@ -13633,11 +12658,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "hidden": { "description": "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", @@ -13659,15 +12680,7 @@ }, { "type": "string", - "enum": [ - "primary", - "secondary", - "accent", - "success", - "warning", - "error", - "info" - ] + "enum": ["primary", "secondary", "accent", "success", "warning", "error", "info"] } ] }, @@ -13753,15 +12766,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -13797,16 +12805,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -13821,10 +12823,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -13833,70 +12832,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -14032,10 +13000,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "command" - ], + "required": ["type", "command"], "additionalProperties": false }, "McpOAuthConfig": { @@ -14101,19 +13066,13 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "url" - ], + "required": ["type", "url"], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": [ - "auto", - "stretch" - ] + "enum": ["auto", "stretch"] }, "Config": { "type": "object", @@ -14153,9 +13112,7 @@ "type": "boolean" } }, - "required": [ - "template" - ] + "required": ["template"] } }, "skills": { @@ -14201,11 +13158,7 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": [ - "manual", - "auto", - "disabled" - ] + "enum": ["manual", "auto", "disabled"] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -14360,9 +13313,7 @@ "type": "boolean" } }, - "required": [ - "enabled" - ], + "required": ["enabled"], "additionalProperties": false } ] @@ -14432,9 +13383,7 @@ "const": true } }, - "required": [ - "disabled" - ] + "required": ["disabled"] }, { "type": "object", @@ -14471,9 +13420,7 @@ "additionalProperties": {} } }, - "required": [ - "command" - ] + "required": ["command"] } ] } @@ -14590,11 +13537,7 @@ "const": false } }, - "required": [ - "data", - "errors", - "success" - ] + "required": ["data", "errors", "success"] }, "OAuth": { "type": "object", @@ -14619,12 +13562,7 @@ "type": "string" } }, - "required": [ - "type", - "refresh", - "access", - "expires" - ] + "required": ["type", "refresh", "access", "expires"] }, "ApiAuth": { "type": "object", @@ -14637,10 +13575,7 @@ "type": "string" } }, - "required": [ - "type", - "key" - ] + "required": ["type", "key"] }, "WellKnownAuth": { "type": "object", @@ -14656,11 +13591,7 @@ "type": "string" } }, - "required": [ - "type", - "key", - "token" - ] + "required": ["type", "key", "token"] }, "Auth": { "anyOf": [ @@ -14689,15 +13620,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "Model": { "type": "object", @@ -14721,11 +13647,7 @@ "type": "string" } }, - "required": [ - "id", - "url", - "npm" - ] + "required": ["id", "url", "npm"] }, "name": { "type": "string" @@ -14767,13 +13689,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "output": { "type": "object", @@ -14794,13 +13710,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "interleaved": { "anyOf": [ @@ -14812,28 +13722,15 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ] + "required": ["field"] } ] } }, - "required": [ - "temperature", - "reasoning", - "attachment", - "toolcall", - "input", - "output", - "interleaved" - ] + "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"] }, "cost": { "type": "object", @@ -14854,10 +13751,7 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] }, "experimentalOver200K": { "type": "object", @@ -14878,24 +13772,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] }, "limit": { "type": "object", @@ -14910,19 +13793,11 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated", - "active" - ] + "enum": ["alpha", "beta", "deprecated", "active"] }, "options": { "type": "object", @@ -14961,26 +13836,14 @@ }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] } }, "required": [ @@ -15008,12 +13871,7 @@ }, "source": { "type": "string", - "enum": [ - "env", - "config", - "custom", - "api" - ] + "enum": ["env", "config", "custom", "api"] }, "env": { "type": "array", @@ -15041,14 +13899,7 @@ } } }, - "required": [ - "id", - "name", - "source", - "env", - "options", - "models" - ] + "required": ["id", "name", "source", "env", "options", "models"] }, "ToolIDs": { "type": "array", @@ -15067,11 +13918,7 @@ }, "parameters": {} }, - "required": [ - "id", - "description", - "parameters" - ] + "required": ["id", "description", "parameters"] }, "ToolList": { "type": "array", @@ -15131,15 +13978,7 @@ "type": "string" } }, - "required": [ - "id", - "type", - "branch", - "name", - "directory", - "extra", - "projectID" - ] + "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] }, "Worktree": { "type": "object", @@ -15154,11 +13993,7 @@ "type": "string" } }, - "required": [ - "name", - "branch", - "directory" - ] + "required": ["name", "branch", "directory"] }, "WorktreeCreateInput": { "type": "object", @@ -15179,9 +14014,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeResetInput": { "type": "object", @@ -15190,9 +14023,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeDiffItem": { "type": "object", @@ -15214,11 +14045,7 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] }, "tracked": { "type": "boolean" @@ -15258,10 +14085,7 @@ "type": "string" } }, - "required": [ - "id", - "worktree" - ] + "required": ["id", "worktree"] }, "GlobalSession": { "type": "object", @@ -15314,26 +14138,14 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "additions", - "deletions" - ] + "required": ["file", "additions", "deletions"] } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -15342,9 +14154,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -15368,10 +14178,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -15392,9 +14199,7 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] }, "project": { "anyOf": [ @@ -15407,16 +14212,7 @@ ] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time", - "project" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] }, "McpResource": { "type": "object", @@ -15437,11 +14233,7 @@ "type": "string" } }, - "required": [ - "name", - "uri", - "client" - ] + "required": ["name", "uri", "client"] }, "TextPartInput": { "type": "object", @@ -15472,9 +14264,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -15484,10 +14274,7 @@ "additionalProperties": {} } }, - "required": [ - "type", - "text" - ] + "required": ["type", "text"] }, "FilePartInput": { "type": "object", @@ -15512,11 +14299,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] }, "AgentPartInput": { "type": "object", @@ -15548,17 +14331,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "type", - "name" - ] + "required": ["type", "name"] }, "SubtaskPartInput": { "type": "object", @@ -15589,21 +14365,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "type", - "prompt", - "description", - "agent" - ] + "required": ["type", "prompt", "description", "agent"] }, "ProviderAuthMethod": { "type": "object", @@ -15624,10 +14392,7 @@ "type": "string" } }, - "required": [ - "type", - "label" - ] + "required": ["type", "label"] }, "ProviderAuthAuthorization": { "type": "object", @@ -15651,11 +14416,7 @@ "type": "string" } }, - "required": [ - "url", - "method", - "instructions" - ] + "required": ["url", "method", "instructions"] }, "Symbol": { "type": "object", @@ -15676,17 +14437,10 @@ "$ref": "#/components/schemas/Range" } }, - "required": [ - "uri", - "range" - ] + "required": ["uri", "range"] } }, - "required": [ - "name", - "kind", - "location" - ] + "required": ["name", "kind", "location"] }, "FileNode": { "type": "object", @@ -15702,32 +14456,20 @@ }, "type": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] }, "ignored": { "type": "boolean" } }, - "required": [ - "name", - "path", - "absolute", - "type", - "ignored" - ] + "required": ["name", "path", "absolute", "type", "ignored"] }, "FileContent": { "type": "object", "properties": { "type": { "type": "string", - "enum": [ - "text", - "binary" - ] + "enum": ["text", "binary"] }, "content": { "type": "string" @@ -15774,24 +14516,14 @@ } } }, - "required": [ - "oldStart", - "oldLines", - "newStart", - "newLines", - "lines" - ] + "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] } }, "index": { "type": "string" } }, - "required": [ - "oldFileName", - "newFileName", - "hunks" - ] + "required": ["oldFileName", "newFileName", "hunks"] }, "encoding": { "type": "string", @@ -15801,10 +14533,7 @@ "type": "string" } }, - "required": [ - "type", - "content" - ] + "required": ["type", "content"] }, "File": { "type": "object", @@ -15824,19 +14553,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "path", - "added", - "removed", - "status" - ] + "required": ["path", "added", "removed", "status"] }, "MCPStatusConnected": { "type": "object", @@ -15846,9 +14566,7 @@ "const": "connected" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusDisabled": { "type": "object", @@ -15858,9 +14576,7 @@ "const": "disabled" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusFailed": { "type": "object", @@ -15873,10 +14589,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatusNeedsAuth": { "type": "object", @@ -15886,9 +14599,7 @@ "const": "needs_auth" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -15901,10 +14612,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatus": { "anyOf": [ @@ -15944,13 +14652,7 @@ "type": "string" } }, - "required": [ - "home", - "state", - "config", - "worktree", - "directory" - ] + "required": ["home", "state", "config", "worktree", "directory"] }, "VcsInfo": { "type": "object", @@ -15959,9 +14661,7 @@ "type": "string" } }, - "required": [ - "branch" - ] + "required": ["branch"] }, "Command": { "type": "object", @@ -15980,11 +14680,7 @@ }, "source": { "type": "string", - "enum": [ - "command", - "mcp", - "skill" - ] + "enum": ["command", "mcp", "skill"] }, "template": { "anyOf": [ @@ -16006,11 +14702,7 @@ } } }, - "required": [ - "name", - "template", - "hints" - ] + "required": ["name", "template", "hints"] }, "Agent": { "type": "object", @@ -16026,11 +14718,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "native": { "type": "boolean" @@ -16063,10 +14751,7 @@ "type": "string" } }, - "required": [ - "modelID", - "providerID" - ] + "required": ["modelID", "providerID"] }, "variant": { "type": "string" @@ -16087,12 +14772,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "name", - "mode", - "permission", - "options" - ] + "required": ["name", "mode", "permission", "options"] }, "LSPStatus": { "type": "object", @@ -16119,12 +14799,7 @@ ] } }, - "required": [ - "id", - "name", - "root", - "status" - ] + "required": ["id", "name", "root", "status"] }, "FormatterStatus": { "type": "object", @@ -16142,12 +14817,8 @@ "type": "boolean" } }, - "required": [ - "name", - "extensions", - "enabled" - ] + "required": ["name", "extensions", "enabled"] } } } -} \ No newline at end of file +} From edc4060f842f7ac67a2c00c06cfcd2754a8a6a5b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 6 Apr 2026 18:03:20 +0300 Subject: [PATCH 18/33] fix(cli): keep auto-approve in the command palette Remove the allow-everything action from permission prompts so ctrl+p is the only path for global approval changes. Add a hint for protected config edits that points users to the command palette toggle. --- packages/opencode/src/cli/cmd/tui/app.tsx | 2 +- .../cli/cmd/tui/routes/session/permission.tsx | 51 +++++-------------- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index efcc50b6f88..9fdc496e3d0 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -704,7 +704,7 @@ function App() { }, // kilocode_change start { - title: kv.get("allow_everything_global") ? "Disable allow everything" : "Enable allow everything", + title: kv.get("allow_everything_global") ? "Disable auto-approve mode" : "Enable auto-approve mode", value: "permission.allow_everything", category: "System", onSelect: (dialog) => { diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index ba96923833d..d5036db644f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -6,7 +6,6 @@ import { useKeybind } from "../../context/keybind" import { useTheme, selectedForeground } from "../../context/theme" import type { PermissionRequest } from "@kilocode/sdk/v2" import { useSDK } from "../../context/sdk" -import { useKV } from "../../context/kv" // kilocode_change import { SplitBorder } from "../../component/border" import { useSync } from "../../context/sync" import { useTextareaKeybindings } from "../../component/textarea-keybindings" @@ -19,7 +18,7 @@ import { useDialog } from "../../ui/dialog" import { useTuiConfig } from "../../context/tui-config" import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change -type PermissionStage = "permission" | "always" | "everything" | "reject" // kilocode_change +type PermissionStage = "permission" | "always" | "reject" function normalizePath(input?: string) { if (!input) return "" @@ -131,7 +130,6 @@ function TextBody(props: { title: string; description?: string; icon?: string }) export function PermissionPrompt(props: { request: PermissionRequest }) { const sdk = useSDK() const sync = useSync() - const kv = useKV() // kilocode_change const [store, setStore] = createStore({ stage: "permission" as PermissionStage, }) @@ -151,6 +149,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { }) const { theme } = useTheme() + const keybind = useKeybind() return ( @@ -193,33 +192,6 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { }} /> - {/* kilocode_change start */} - - } - options={{ session: "This session", all: "All sessions", cancel: "Cancel" }} - escapeKey="cancel" - onSelect={(option) => { - setStore("stage", "permission") - if (option === "cancel") return - if (option === "session") { - sdk.client.permission.allowEverything({ - enable: true, - requestID: props.request.id, - sessionID: props.request.sessionID, - }) - return - } - sdk.client.permission.allowEverything({ - enable: true, - requestID: props.request.id, - }) - kv.set("allow_everything_global", true) - }} - /> - - {/* kilocode_change end */} { @@ -455,13 +427,20 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {current.title} + {/* kilocode_change start - point config protection prompts to command palette */} + + + Use {keybind.print("command_list")} to toggle auto-approve mode + + + {/* kilocode_change end */} ) - // kilocode_change start — hide "Always allow" for config file edits, add "Allow everything" + // kilocode_change start — hide "Always allow" for config file edits const options: Record = props.request.metadata?.[ConfigProtection.DISABLE_ALWAYS_KEY] - ? { once: "Allow once", everything: "Allow everything", reject: "Reject" } - : { once: "Allow once", always: "Allow always", everything: "Allow everything", reject: "Reject" } + ? { once: "Allow once", reject: "Reject" } + : { once: "Allow once", always: "Allow always", reject: "Reject" } // kilocode_change end const body = ( @@ -477,12 +456,6 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { setStore("stage", "always") return } - // kilocode_change start - if (option === "everything") { - setStore("stage", "everything") - return - } - // kilocode_change end if (option === "reject") { if (session()?.parentID) { setStore("stage", "reject") From 6d27af6827093a6538bee848dfc62316e6c97ca5 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 6 Apr 2026 19:25:53 +0300 Subject: [PATCH 19/33] fix(cli): scope allow-all approvals to the active session --- packages/opencode/src/permission/next.ts | 8 ++- .../opencode/test/permission/next.test.ts | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 281fc7de6ac..a32244140f4 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -179,6 +179,7 @@ export namespace PermissionNext { return { pending, approved: stored, + session: {} as Record, } }) @@ -189,11 +190,12 @@ export namespace PermissionNext { async (input) => { const s = await state() const { ruleset, ...request } = input + const local = s.session[request.sessionID] ?? [] // kilocode_change start — force "ask" for config file edits const protected_ = ConfigProtection.isRequest(request) // kilocode_change end for (const pattern of request.patterns ?? []) { - const rule = evaluate(request.permission, pattern, ruleset, s.approved) + const rule = evaluate(request.permission, pattern, ruleset, s.approved, local) log.info("evaluated", { permission: request.permission, pattern, action: rule }) if (rule.action === "deny") throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) @@ -371,7 +373,9 @@ export namespace PermissionNext { return } - s.approved.push({ permission: "*", pattern: "*", action: "allow" }) + const rule = { permission: "*", pattern: "*", action: "allow" } as const + if (input.sessionID) s.session[input.sessionID] = [rule] + else s.approved.push(rule) if (input.requestID) { const existing = s.pending[input.requestID] diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index add3332048c..40786c27048 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -606,6 +606,59 @@ test("reply - always persists approval and resolves", async () => { }) }) +test("allowEverything - session-scoped enable stays within one session", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const first = PermissionNext.ask({ + id: "permission_session_allow", + sessionID: "session_allowed", + permission: "bash", + patterns: ["pwd"], + metadata: {}, + always: [], + ruleset: [], + }) + + await PermissionNext.allowEverything({ + enable: true, + requestID: "permission_session_allow", + sessionID: "session_allowed", + }) + + await expect(first).resolves.toBeUndefined() + + const allowed = await PermissionNext.ask({ + sessionID: "session_allowed", + permission: "bash", + patterns: ["ls"], + metadata: {}, + always: [], + ruleset: [], + }) + expect(allowed).toBeUndefined() + + const blocked = PermissionNext.ask({ + id: "permission_session_blocked", + sessionID: "session_blocked", + permission: "bash", + patterns: ["ls"], + metadata: {}, + always: [], + ruleset: [], + }) + + await PermissionNext.reply({ + requestID: "permission_session_blocked", + reply: "reject", + }) + + await expect(blocked).rejects.toBeInstanceOf(PermissionNext.RejectedError) + }, + }) +}) + test("reply - reject cancels all pending for same session", async () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({ From 4de08a1027199c559bb0f1513c62cadb113f3b6f Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 6 Apr 2026 19:26:31 +0300 Subject: [PATCH 20/33] fix(cli): disable allow-all in the requested scope --- packages/opencode/src/permission/next.ts | 4 + .../opencode/src/server/routes/permission.ts | 12 +++ .../permission-allow-everything.test.ts | 77 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 packages/opencode/test/server/permission-allow-everything.test.ts diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index a32244140f4..fa61113b692 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -368,6 +368,10 @@ export namespace PermissionNext { const s = await state() if (!input.enable) { + if (input.sessionID) { + delete s.session[input.sessionID] + return + } const idx = s.approved.findLastIndex((r) => r.permission === "*" && r.pattern === "*" && r.action === "allow") if (idx >= 0) s.approved.splice(idx, 1) return diff --git a/packages/opencode/src/server/routes/permission.ts b/packages/opencode/src/server/routes/permission.ts index 6bd9e18af87..8e0e2002f15 100644 --- a/packages/opencode/src/server/routes/permission.ts +++ b/packages/opencode/src/server/routes/permission.ts @@ -127,6 +127,18 @@ export const PermissionRoutes = lazy(() => const rules: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "allow" }] if (!json.enable) { + if (json.sessionID) { + const session = await Session.get(json.sessionID) + await Session.setPermission({ + sessionID: json.sessionID, + permission: (session.permission ?? []).filter( + (rule) => !(rule.permission === "*" && rule.pattern === "*" && rule.action === "allow"), + ), + }) + await PermissionNext.allowEverything({ enable: false, sessionID: json.sessionID }) + return c.json(true) + } + await Config.updateGlobal({ permission: { "*": { "*": null } } }, { dispose: false }) await PermissionNext.allowEverything({ enable: false }) return c.json(true) diff --git a/packages/opencode/test/server/permission-allow-everything.test.ts b/packages/opencode/test/server/permission-allow-everything.test.ts new file mode 100644 index 00000000000..28386e01428 --- /dev/null +++ b/packages/opencode/test/server/permission-allow-everything.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, test } from "bun:test" +import { PermissionNext } from "../../src/permission/next" +import { Instance } from "../../src/project/instance" +import { Server } from "../../src/server/server" +import { Session } from "../../src/session" +import { tmpdir } from "../fixture/fixture" + +describe("permission.allowEverything endpoint", () => { + test("disables session-scoped allow-all without touching global config", async () => { + await using tmp = await tmpdir({ git: true }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const app = Server.App() + const session = await Session.create({ + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + + await PermissionNext.allowEverything({ + enable: true, + sessionID: session.id, + }) + + const response = await app.request("/permission/allow-everything", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-kilo-directory": tmp.path, + }, + body: JSON.stringify({ enable: false, sessionID: session.id }), + }) + + expect(response.status).toBe(200) + expect(await response.json()).toBe(true) + + const next = await Session.get(session.id) + expect(next.permission ?? []).toEqual([]) + + const pending = PermissionNext.ask({ + id: "permission_session_disable", + sessionID: session.id, + permission: "bash", + patterns: ["ls"], + metadata: {}, + always: [], + ruleset: [], + }) + + await PermissionNext.reply({ + requestID: "permission_session_disable", + reply: "reject", + }) + + await expect(pending).rejects.toBeInstanceOf(PermissionNext.RejectedError) + + const other = await Session.create({}) + const blocked = PermissionNext.ask({ + id: "permission_other_session", + sessionID: other.id, + permission: "bash", + patterns: ["pwd"], + metadata: {}, + always: [], + ruleset: [], + }) + + await PermissionNext.reply({ + requestID: "permission_other_session", + reply: "reject", + }) + + await expect(blocked).rejects.toBeInstanceOf(PermissionNext.RejectedError) + }, + }) + }) +}) From 4e24bc63ce01bd743c1275092564175bd110fdd1 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 6 Apr 2026 19:27:18 +0300 Subject: [PATCH 21/33] fix(cli): keep the auto-approve toggle in sync --- packages/opencode/src/cli/cmd/tui/app.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 9fdc496e3d0..4c361f810dc 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -707,10 +707,18 @@ function App() { title: kv.get("allow_everything_global") ? "Disable auto-approve mode" : "Enable auto-approve mode", value: "permission.allow_everything", category: "System", - onSelect: (dialog) => { + onSelect: async (dialog) => { const enabled = kv.get("allow_everything_global") - sdk.client.permission.allowEverything({ enable: !enabled }) - kv.set("allow_everything_global", !enabled) + const next = !enabled + const result = await sdk.client.permission.allowEverything({ enable: next }) + if (result.error) { + toast.show({ + variant: "error", + message: `Failed to ${next ? "enable" : "disable"} auto-approve mode`, + }) + return + } + kv.set("allow_everything_global", next) dialog.clear() }, }, From 99da10cba340eaee9e4678b613c47304ef980364 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 12:15:55 +0300 Subject: [PATCH 22/33] fix(cli): read allow-everything state from config instead of local KV store The ctrl+p auto-approve toggle was tracking state in a local KV file that was never initialized from the actual config, causing the label to be out of sync. Now reads directly from sync.data.config.permission and listens for global.config.updated SSE events to stay reactive. --- packages/opencode/src/cli/cmd/tui/app.tsx | 22 ++++++++++++++----- .../opencode/src/cli/cmd/tui/context/sync.tsx | 9 ++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index f38ec371c0f..61b64d1a789 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -48,6 +48,16 @@ import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui" // kiloco import { TuiConfigProvider } from "./context/tui-config" import { TuiConfig } from "@/config/tui" +// kilocode_change start +function isAllowEverything(permission: unknown): boolean { + if (typeof permission !== "object" || permission === null) return false + const wildcard = (permission as Record)["*"] + if (typeof wildcard === "string") return wildcard === "allow" + if (typeof wildcard === "object" && wildcard !== null) return (wildcard as Record)["*"] === "allow" + return false +} +// kilocode_change end + async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY if (!process.stdin.isTTY) return "dark" @@ -711,21 +721,21 @@ function App() { }, // kilocode_change start { - title: kv.get("allow_everything_global") ? "Disable auto-approve mode" : "Enable auto-approve mode", + get title() { + return isAllowEverything(sync.data.config.permission) ? "Disable auto-approve mode" : "Enable auto-approve mode" + }, value: "permission.allow_everything", category: "System", onSelect: async (dialog) => { - const enabled = kv.get("allow_everything_global") - const next = !enabled - const result = await sdk.client.permission.allowEverything({ enable: next }) + const enabled = isAllowEverything(sync.data.config.permission) + const result = await sdk.client.permission.allowEverything({ enable: !enabled }) if (result.error) { toast.show({ variant: "error", - message: `Failed to ${next ? "enable" : "disable"} auto-approve mode`, + message: `Failed to ${!enabled ? "enable" : "disable"} auto-approve mode`, }) return } - kv.set("allow_everything_global", next) dialog.clear() }, }, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index e7049fcbe4e..985bc7a4d75 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -390,6 +390,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ setStore("vcs", { branch: event.properties.branch }) break } + + // kilocode_change start + case "global.config.updated": { + sdk.client.config.get().then((x) => { + if (x.data) setStore("config", reconcile(x.data)) + }) + break + } + // kilocode_change end } }) From c4ebdb8a96185c59404c051e1d2f2d4f6a54ad93 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:19:58 +0300 Subject: [PATCH 23/33] fix(cli): check session ownership on requestID --- packages/opencode/src/permission/next.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index fa61113b692..ddf83c3c689 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -383,7 +383,7 @@ export namespace PermissionNext { if (input.requestID) { const existing = s.pending[input.requestID] - if (existing) { + if (existing && (!input.sessionID || existing.info.sessionID === input.sessionID)) { delete s.pending[input.requestID] Bus.publish(Event.Replied, { sessionID: existing.info.sessionID, From 6a4df6669fd34ceeee6bd32f4116c0be63cfd0cd Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:20:40 +0300 Subject: [PATCH 24/33] fix(cli): correct misleading config hint --- .../opencode/src/cli/cmd/tui/routes/session/permission.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index d5036db644f..823d17e1836 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -427,10 +427,10 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {current.title} - {/* kilocode_change start - point config protection prompts to command palette */} + {/* kilocode_change start - explain config file edits always require approval */} - Use {keybind.print("command_list")} to toggle auto-approve mode + Config file edits always require approval {/* kilocode_change end */} From b89513fd8da2e1116313224e42e4b31a97ea451e Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 16:16:27 +0300 Subject: [PATCH 25/33] feat(cli): show worktree labels in session list with ctrl+a filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show all worktree sessions by default with directory basename labels. ctrl+a toggles to current worktree only. Uses experimental endpoint exclusively — no shared OpenCode code changes. --- .../cmd/tui/component/dialog-session-list.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index c37c737ab3c..dedd30a3655 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -10,6 +10,7 @@ import { useSDK } from "../context/sdk" import { DialogSessionRename } from "./dialog-session-rename" import { createDebouncedSignal } from "../util/signal" import { Spinner } from "./spinner" +import path from "path" // kilocode_change export function DialogSessionList() { const dialog = useDialog() @@ -21,26 +22,21 @@ export function DialogSessionList() { const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) - const [global, setGlobal] = createSignal(false) // kilocode_change + const [global, setGlobal] = createSignal(true) // kilocode_change - show all worktrees by default - // kilocode_change start + // kilocode_change start - always fetch from experimental endpoint (returns GlobalSession with worktree info) const [searchResults, searchActions] = createResource( - () => ({ query: search(), global: global() }), - async ({ query, global: all }) => { - if (!query && !all) return undefined - if (all) { - const result = await sdk.client.experimental.session.list( - { - search: query || undefined, - roots: true, - worktrees: true, - limit: 30, - }, - { throwOnError: true }, - ) - return result.data ?? [] - } - const result = await sdk.client.session.list({ search: query || undefined, limit: 30 }, { throwOnError: true }) + () => search(), + async (query) => { + const result = await sdk.client.experimental.session.list( + { + search: query || undefined, + roots: true, + worktrees: true, + limit: 30, + }, + { throwOnError: true }, + ) return result.data ?? [] }, ) @@ -48,10 +44,15 @@ export function DialogSessionList() { const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) + // kilocode_change start - client-side worktree filtering when global is off const sessions = createMemo(() => { - if (global() || search()) return searchResults() ?? [] // kilocode_change - return searchResults() ?? sync.data.session + const all = searchResults() ?? [] + if (global()) return all + const root = sync.data.path.worktree + if (!root || root === "/") return all + return all.filter((s) => s.directory === root || s.directory.startsWith(root + path.sep)) }) + // kilocode_change end const options = createMemo(() => { const today = new Date().toDateString() @@ -70,6 +71,7 @@ export function DialogSessionList() { const isWorking = status?.type === "busy" return { title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, + description: all ? `(${path.basename(x.directory)})` : undefined, // kilocode_change - worktree label bg: isDeleting ? theme.error : undefined, value: x.id, category, @@ -85,7 +87,7 @@ export function DialogSessionList() { return ( { - if (global() || search()) void searchActions.refetch() // kilocode_change + void searchActions.refetch() // kilocode_change }} /> )) @@ -135,7 +137,7 @@ export function DialogSessionList() { // kilocode_change start { keybind: { name: "a", ctrl: true, meta: false, shift: false, leader: false }, - title: global() ? "recent" : "all", + title: global() ? "current" : "all", onTrigger: async () => { setToDelete(undefined) setGlobal((v) => !v) From afc858af6849fbd0552714075e6d268e8a71d90b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 16:26:25 +0300 Subject: [PATCH 26/33] fix(cli): resolve worktree folder name for session list labels Instead of path.basename(session.directory) which shows the working directory name (e.g. 'opencode' for sessions in packages/opencode/), resolve the actual worktree root by matching against WorktreeFamily.list() directories. This shows the correct worktree folder name (e.g. 'kilocode', 'cross-session-conflict') matching git worktree list output. --- .../cli/cmd/tui/component/dialog-session-list.tsx | 2 +- packages/opencode/src/server/routes/experimental.ts | 12 ++++++++++++ packages/opencode/src/session/index.ts | 1 + packages/sdk/js/src/v2/gen/types.gen.ts | 1 + packages/sdk/openapi.json | 6 ++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index dedd30a3655..e063844c005 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -71,7 +71,7 @@ export function DialogSessionList() { const isWorking = status?.type === "busy" return { title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, - description: all ? `(${path.basename(x.directory)})` : undefined, // kilocode_change - worktree label + description: all && x.worktreeName ? `(${x.worktreeName})` : undefined, // kilocode_change - worktree label bg: isDeleting ? theme.error : undefined, value: x.id, category, diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index 1acd7be0039..268b4d0c976 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -16,6 +16,8 @@ import { WorktreeDiff } from "../../kilocode/review/worktree-diff" // kilocode_c import { WorktreeFamily } from "../../kilocode/worktree-family" // kilocode_change import { Log } from "../../util/log" // kilocode_change import { WorkspaceRoutes } from "./workspace" +import { Filesystem } from "../../util/filesystem" // kilocode_change +import path from "path" // kilocode_change export const ExperimentalRoutes = lazy(() => new Hono() @@ -356,6 +358,9 @@ export const ExperimentalRoutes = lazy(() => ? Instance.project.id // kilocode_change : query.projectID const directories = query.worktrees ? await WorktreeFamily.list() : undefined // kilocode_change + // kilocode_change start - sort longest-first so most specific worktree matches first + const sorted = directories ? [...directories].sort((a, b) => b.length - a.length) : undefined + // kilocode_change end const sessions: Session.GlobalInfo[] = [] for await (const session of Session.listGlobal({ projectID, // kilocode_change @@ -368,6 +373,13 @@ export const ExperimentalRoutes = lazy(() => limit: limit + 1, archived: query.archived, })) { + // kilocode_change start - resolve worktree folder name for each session + if (sorted) { + const root = sorted.find((d) => Filesystem.contains(d, session.directory)) + sessions.push({ ...session, worktreeName: path.basename(root ?? session.directory) }) + continue + } + // kilocode_change end sessions.push(session) } const hasMore = sessions.length > limit diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 4eca075c919..dc4ee8529ed 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -204,6 +204,7 @@ export namespace Session { export const GlobalInfo = Info.extend({ project: ProjectInfo.nullable(), + worktreeName: z.string().optional(), // kilocode_change - basename of the specific worktree directory }).meta({ ref: "GlobalSession", }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 3c0fd23c396..2a49963f81f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1780,6 +1780,7 @@ export type GlobalSession = { diff?: string } project: ProjectSummary | null + worktreeName?: string } export type McpResource = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index a2e81ed9b52..11046f7c45f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5818,6 +5818,9 @@ "projectID": { "type": "string" }, + "force": { + "type": "boolean" + }, "workspaceID": { "type": "string" }, @@ -14210,6 +14213,9 @@ "type": "null" } ] + }, + "worktreeName": { + "type": "string" } }, "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] From 42448f98cd8c011024a3ff32e039888c7860dcd0 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 16:43:45 +0300 Subject: [PATCH 27/33] chore(cli): add missing kilocode_change annotations Add kilocode_change markers to all Kilo-specific changes in shared opencode files to pass the check-opencode-annotations CI check. Also bring in the workflow and script from main. --- .../workflows/check-opencode-annotations.yml | 32 ++++ packages/opencode/src/cli/cmd/session.ts | 18 +- .../cmd/tui/component/dialog-session-list.tsx | 10 +- .../tui/component/dialog-session-rename.tsx | 4 +- .../src/server/routes/experimental.ts | 12 +- packages/opencode/src/session/index.ts | 9 +- .../server/experimental-session-list.test.ts | 1 + .../test/server/global-session-list.test.ts | 1 + packages/opencode/test/tool/recall.test.ts | 1 + script/check-opencode-annotations.ts | 168 ++++++++++++++++++ 10 files changed, 236 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/check-opencode-annotations.yml create mode 100644 script/check-opencode-annotations.ts diff --git a/.github/workflows/check-opencode-annotations.yml b/.github/workflows/check-opencode-annotations.yml new file mode 100644 index 00000000000..b2fc705daea --- /dev/null +++ b/.github/workflows/check-opencode-annotations.yml @@ -0,0 +1,32 @@ +name: Check opencode annotations + +on: + pull_request: + paths: + - "packages/opencode/**" + - "script/check-opencode-annotations.ts" + - ".github/workflows/check-opencode-annotations.yml" + workflow_dispatch: + +jobs: + check-annotations: + name: Check kilocode_change annotations + if: github.repository == 'Kilo-Org/kilocode' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + + - uses: oven-sh/setup-bun@v2 + + - name: Check kilocode_change annotations in shared opencode files + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + if [ -n "$BASE_SHA" ]; then + bun run script/check-opencode-annotations.ts --base "$BASE_SHA" + else + echo "No PR base SHA available (workflow_dispatch without PR context) — skipping." + fi diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index ec62ab93d69..98684108793 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -73,6 +73,7 @@ export const SessionListCommand = cmd({ command: "list", describe: "list sessions", builder: (yargs: Argv) => { + // kilocode_change start return ( yargs .option("max-count", { @@ -86,6 +87,7 @@ export const SessionListCommand = cmd({ choices: ["table", "json"], default: "table", }) + // kilocode_change end // kilocode_change start .option("all", { alias: "a", @@ -101,27 +103,29 @@ export const SessionListCommand = cmd({ ) // kilocode_change end }, + // kilocode_change start handler: async (args) => { await bootstrap(process.cwd(), async () => { + // kilocode_change end // kilocode_change start const sessions = args.all ? [...Session.listGlobal({ roots: true, limit: args.maxCount, search: args.search })] : [...Session.list({ roots: true, limit: args.maxCount, search: args.search })] // kilocode_change end + // kilocode_change start if (sessions.length === 0) { return } + // kilocode_change end + // kilocode_change start let output: string if (args.format === "json") { - // kilocode_change start output = args.all ? formatGlobalSessionJSON(sessions as Session.GlobalInfo[]) : formatSessionJSON(sessions as Session.Info[]) - // kilocode_change end } else { - // kilocode_change start output = args.all ? formatGlobalSessionTable(sessions as Session.GlobalInfo[]) : formatSessionTable(sessions as Session.Info[]) @@ -178,10 +182,10 @@ function formatSessionJSON(sessions: Session.Info[]): string { updated: session.time.updated, created: session.time.created, projectId: session.projectID, - directory: session.directory, - })) - return JSON.stringify(jsonData, null, 2) -} + directory: session.directory, // kilocode_change + })) // kilocode_change + return JSON.stringify(jsonData, null, 2) // kilocode_change +} // kilocode_change // kilocode_change start function formatGlobalSessionTable(sessions: Session.GlobalInfo[]): string { diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index e063844c005..8ad7d001494 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,7 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { createMemo, createSignal, createResource, onMount } from "solid-js" +import { createMemo, createSignal, createResource, onMount } from "solid-js" // kilocode_change import { Locale } from "@/util/locale" import { useKeybind } from "../context/keybind" import { useTheme } from "../context/theme" @@ -108,9 +108,11 @@ export function DialogSessionList() { title: "delete", onTrigger: async (option) => { if (toDelete() === option.value) { + // kilocode_change start await sdk.client.session.delete({ sessionID: option.value, }) + // kilocode_change end setToDelete(undefined) void searchActions.refetch() // kilocode_change return @@ -120,7 +122,8 @@ export function DialogSessionList() { }, { keybind: keybind.all.session_rename?.[0], - title: "rename", + title: "rename", // kilocode_change + // kilocode_change start onTrigger: async (option) => { const item = sessions().find((x) => x.id === option.value) dialog.replace(() => ( @@ -128,13 +131,12 @@ export function DialogSessionList() { session={option.value} title={item?.title} onConfirm={() => { - void searchActions.refetch() // kilocode_change + void searchActions.refetch() }} /> )) }, }, - // kilocode_change start { keybind: { name: "a", ctrl: true, meta: false, shift: false, leader: false }, title: global() ? "current" : "all", diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx index be4b8f65dd2..c519f41279d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx @@ -21,12 +21,14 @@ export function DialogSessionRename(props: DialogSessionRenameProps) { title="Rename Session" value={session()?.title ?? props.title} // kilocode_change onConfirm={(value) => { + // kilocode_change start sdk.client.session .update({ sessionID: props.session, title: value, }) - .then(() => props.onConfirm?.()) // kilocode_change + .then(() => props.onConfirm?.()) + // kilocode_change end dialog.clear() }} onCancel={() => dialog.clear()} diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index 268b4d0c976..c16704456b6 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -329,13 +329,14 @@ export const ExperimentalRoutes = lazy(() => validator( "query", z.object({ - // kilocode_change + // kilocode_change start projectID: z.string().optional().meta({ description: "Filter sessions by project ID" }), directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), worktrees: z.coerce .boolean() .optional() .meta({ description: "Restrict sessions to the current repo worktree family or current directory" }), + // kilocode_change end roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), start: z.coerce .number() @@ -352,11 +353,10 @@ export const ExperimentalRoutes = lazy(() => ), async (c) => { const query = c.req.valid("query") - const limit = query.limit ?? 100 - const projectID = - query.worktrees && !query.projectID - ? Instance.project.id // kilocode_change - : query.projectID + const limit = query.limit ?? 100 // kilocode_change + // kilocode_change start + const projectID = query.worktrees && !query.projectID ? Instance.project.id : query.projectID + // kilocode_change end const directories = query.worktrees ? await WorktreeFamily.list() : undefined // kilocode_change // kilocode_change start - sort longest-first so most specific worktree matches first const sorted = directories ? [...directories].sort((a, b) => b.length - a.length) : undefined diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index dc4ee8529ed..b64225c5d0d 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -641,6 +641,7 @@ export namespace Session { } } + // kilocode_change start export function* listGlobal(input?: { projectID?: string directory?: string @@ -652,7 +653,7 @@ export namespace Session { limit?: number archived?: boolean }) { - const conditions: SQL[] = [] + const conditions: SQL[] = [] // kilocode_change // kilocode_change start if (input?.projectID) { @@ -686,7 +687,8 @@ export namespace Session { conditions.push(isNull(SessionTable.time_archived)) } - const limit = input?.limit ?? 100 + const limit = input?.limit ?? 100 // kilocode_change + // kilocode_change start const dirs = [...new Set((input?.directories ?? []).map((dir) => Filesystem.resolve(dir)))] const rows = Database.use((db) => { @@ -728,11 +730,14 @@ export namespace Session { }) } } + // kilocode_change end + // kilocode_change start for (const row of list.slice(0, limit)) { const project = projects.get(row.project_id) ?? null yield { ...fromRow(row), project } } + // kilocode_change end } export const children = fn(Identifier.schema("session"), async (parentID) => { diff --git a/packages/opencode/test/server/experimental-session-list.test.ts b/packages/opencode/test/server/experimental-session-list.test.ts index 2f93482725c..c26ff3917a4 100644 --- a/packages/opencode/test/server/experimental-session-list.test.ts +++ b/packages/opencode/test/server/experimental-session-list.test.ts @@ -1,3 +1,4 @@ +// kilocode_change - new file import { afterEach, describe, expect, mock, test } from "bun:test" import { $ } from "bun" import path from "path" diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 34fa3cea15c..bb2d640bc2e 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -1,3 +1,4 @@ +// kilocode_change - new file import { $ } from "bun" import { afterEach, describe, expect, mock, test } from "bun:test" import path from "path" diff --git a/packages/opencode/test/tool/recall.test.ts b/packages/opencode/test/tool/recall.test.ts index 3275a6e98b7..677923a55e2 100644 --- a/packages/opencode/test/tool/recall.test.ts +++ b/packages/opencode/test/tool/recall.test.ts @@ -1,3 +1,4 @@ +// kilocode_change - new file import { afterEach, describe, expect, mock, test } from "bun:test" import { $ } from "bun" import path from "path" diff --git a/script/check-opencode-annotations.ts b/script/check-opencode-annotations.ts new file mode 100644 index 00000000000..314b399d460 --- /dev/null +++ b/script/check-opencode-annotations.ts @@ -0,0 +1,168 @@ +#!/usr/bin/env bun + +/** + * Verifies that every Kilo-specific change in shared packages/opencode/ files + * is annotated with a kilocode_change marker. + * + * Usage: + * bun run script/check-opencode-annotations.ts # diff against origin/main + * bun run script/check-opencode-annotations.ts --base # diff against + * + * A line is "covered" if it: + * - contains // kilocode_change (inline annotation) + * - falls inside a // kilocode_change start/end block (block annotation) + * - is in a file whose first non-empty line is (whole-file annotation) + * // kilocode_change - new file + * - is empty / whitespace-only (skipped) + * - is itself a marker line (auto-covered) + * + * Exempt paths (no markers needed — entirely Kilo-specific): + * - packages/opencode/src/kilocode/** + * - packages/opencode/test/kilocode/** + * - Any path containing "kilocode" in directory or filename + */ + +import { spawnSync } from "node:child_process" +import { readFileSync } from "node:fs" +import path from "node:path" + +const ROOT = path.resolve(import.meta.dir, "..") +const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"]) + +const args = process.argv.slice(2) +const baseIdx = args.indexOf("--base") +const base = baseIdx !== -1 ? args[baseIdx + 1] : "origin/main" + +function run(cmd: string, args: string[]) { + const result = spawnSync(cmd, args, { cwd: ROOT, encoding: "utf8" }) + return result.stdout?.trim() ?? "" +} + +function changedFiles() { + const out = run("git", ["diff", "--name-only", "--diff-filter=AMRT", `${base}...HEAD`, "--", "packages/opencode"]) + return out ? out.split("\n").filter(Boolean) : [] +} + +function isExempt(file: string) { + const norm = file.replaceAll("\\", "/").toLowerCase() + return norm.split("/").some((part) => part.includes("kilocode")) +} + +function isSource(file: string) { + return SOURCE_EXTS.has(path.extname(file)) +} + +function addedLines(file: string): Set { + const diff = run("git", ["diff", "--unified=0", "--diff-filter=AMRT", `${base}...HEAD`, "--", file]) + const out = new Set() + for (const line of diff.split("\n")) { + const m = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) + if (!m) continue + const start = Number(m[1]) + const count = m[2] !== undefined ? Number(m[2]) : 1 + for (let i = 0; i < count; i++) out.add(start + i) + } + return out +} + +function coveredLines(text: string): { lines: string[]; covered: Set } { + const lines = text.split(/\r?\n/) + const covered = new Set() + + // Whole-file annotation: first non-empty line is "// kilocode_change - new file" + const first = lines.find((x) => x.trim() !== "") + if (first?.match(/\/\/\s*kilocode_change\s*-\s*new\s*file\b/)) { + for (let i = 1; i <= lines.length; i++) covered.add(i) + return { lines, covered } + } + + let block = false + for (let i = 0; i < lines.length; i++) { + const n = i + 1 + const line = lines[i] ?? "" + + if (line.match(/\/\/\s*kilocode_change\s+start\b/)) { + block = true + covered.add(n) + continue + } + + if (line.match(/\/\/\s*kilocode_change\s+end\b/)) { + covered.add(n) + block = false + continue + } + + if (block) { + covered.add(n) + continue + } + + if (line.match(/\/\/\s*kilocode_change\b/)) covered.add(n) + } + + return { lines, covered } +} + +// --- main --- + +const files = changedFiles().filter((f) => !isExempt(f) && isSource(f)) + +if (files.length === 0) { + console.log("No shared opencode source files changed — nothing to check.") + process.exit(0) +} + +const violations: string[] = [] + +for (const file of files) { + const nums = addedLines(file) + if (nums.size === 0) continue + + const abs = path.join(ROOT, file) + const text = readFileSync(abs, "utf8") + const { lines, covered } = coveredLines(text) + + for (const n of nums) { + const line = lines[n - 1] ?? "" + const trim = line.trim() + if (!trim) continue + if (trim.match(/\/\/\s*kilocode_change\b/)) continue + if (!covered.has(n)) violations.push(` ${file}:${n}: ${trim}`) + } +} + +if (violations.length === 0) { + console.log("All shared opencode changes are annotated with kilocode_change markers.") + process.exit(0) +} + +console.error( + [ + "Unannotated Kilo changes found in shared opencode files:", + "", + ...violations, + "", + "Every Kilo-specific change in packages/opencode/ must be annotated.", + "", + "Inline (single line):", + " const url = Flag.KILO_MODELS_URL || 'https://models.dev' // kilocode_change", + "", + "Block (multiple lines):", + " // kilocode_change start", + " ...", + " // kilocode_change end", + "", + "New file:", + " // kilocode_change - new file", + "", + "Exempt paths (no markers needed):", + " - packages/opencode/src/kilocode/**", + " - packages/opencode/test/kilocode/**", + " - Any path containing 'kilocode' in the directory or filename", + "", + "See AGENTS.md for details.", + ].join("\n"), +) + +process.exit(1) From 06c98f8ec5a8e6d3600ac527e6cb1596ae1f5dff Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 16:46:38 +0300 Subject: [PATCH 28/33] chore(cli): wrap formatSessionJSON in kilocode_change block The CI base SHA differs from local main, causing additional lines in formatSessionJSON to be flagged. Wrapping the entire function in a block annotation covers all possible diff boundaries. --- packages/opencode/src/cli/cmd/session.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index 98684108793..a76fb23e579 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -175,6 +175,7 @@ function formatSessionTable(sessions: Session.Info[]): string { return lines.join(EOL) } +// kilocode_change start function formatSessionJSON(sessions: Session.Info[]): string { const jsonData = sessions.map((session) => ({ id: session.id, @@ -182,10 +183,11 @@ function formatSessionJSON(sessions: Session.Info[]): string { updated: session.time.updated, created: session.time.created, projectId: session.projectID, - directory: session.directory, // kilocode_change - })) // kilocode_change - return JSON.stringify(jsonData, null, 2) // kilocode_change -} // kilocode_change + directory: session.directory, + })) + return JSON.stringify(jsonData, null, 2) +} +// kilocode_change end // kilocode_change start function formatGlobalSessionTable(sessions: Session.GlobalInfo[]): string { From 758a6582fe51891d0a4b46352904c9cd7e57e2f7 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 18:50:49 +0300 Subject: [PATCH 29/33] refactor(cli): move allow-everything route to kilocode directory Move the /allow-everything endpoint from the shared permission routes into packages/opencode/src/kilocode/permission/routes.ts to reduce diff with upstream and ease future merges. --- .../src/kilocode/permission/routes.ts | 78 +++++++++++++++++++ .../opencode/src/server/routes/permission.ts | 72 ----------------- packages/opencode/src/server/server.ts | 2 + 3 files changed, 80 insertions(+), 72 deletions(-) create mode 100644 packages/opencode/src/kilocode/permission/routes.ts diff --git a/packages/opencode/src/kilocode/permission/routes.ts b/packages/opencode/src/kilocode/permission/routes.ts new file mode 100644 index 00000000000..3321c2f1090 --- /dev/null +++ b/packages/opencode/src/kilocode/permission/routes.ts @@ -0,0 +1,78 @@ +import { Hono } from "hono" +import { describeRoute, resolver, validator } from "hono-openapi" +import z from "zod" +import { Config } from "@/config/config" +import { PermissionNext } from "@/permission/next" +import { Session } from "@/session" +import { errors } from "../../server/error" +import { lazy } from "../../util/lazy" + +export const PermissionKilocodeRoutes = lazy(() => + new Hono().post( + "/allow-everything", + describeRoute({ + summary: "Allow everything", + description: "Enable or disable allowing all permissions without prompts.", + operationId: "permission.allowEverything", + responses: { + 200: { + description: "Success", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(400, 404), + }, + }), + validator( + "json", + z.object({ + enable: z.boolean(), + requestID: z.string().optional(), + sessionID: z.string().optional(), + }), + ), + async (c) => { + const body = c.req.valid("json") + const rules: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "allow" }] + + if (!body.enable) { + if (body.sessionID) { + const session = await Session.get(body.sessionID) + await Session.setPermission({ + sessionID: body.sessionID, + permission: (session.permission ?? []).filter( + (rule) => !(rule.permission === "*" && rule.pattern === "*" && rule.action === "allow"), + ), + }) + await PermissionNext.allowEverything({ enable: false, sessionID: body.sessionID }) + return c.json(true) + } + + await Config.updateGlobal({ permission: { "*": { "*": null } } }, { dispose: false }) + await PermissionNext.allowEverything({ enable: false }) + return c.json(true) + } + + if (body.sessionID) { + const session = await Session.get(body.sessionID) + await Session.setPermission({ + sessionID: body.sessionID, + permission: [...(session.permission ?? []), ...rules], + }) + } else { + await Config.updateGlobal({ permission: PermissionNext.toConfig(rules) }, { dispose: false }) + } + + await PermissionNext.allowEverything({ + enable: true, + requestID: body.requestID, + sessionID: body.sessionID, + }) + + return c.json(true) + }, + ), +) diff --git a/packages/opencode/src/server/routes/permission.ts b/packages/opencode/src/server/routes/permission.ts index 8e0e2002f15..701de5a098e 100644 --- a/packages/opencode/src/server/routes/permission.ts +++ b/packages/opencode/src/server/routes/permission.ts @@ -2,8 +2,6 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" import { PermissionNext } from "@/permission/next" -import { Session } from "@/session" // kilocode_change -import { Config } from "@/config/config" // kilocode_change import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -95,76 +93,6 @@ export const PermissionRoutes = lazy(() => }, ) // kilocode_change end - // kilocode_change start - .post( - "/allow-everything", - describeRoute({ - summary: "Allow everything", - description: "Enable or disable allowing all permissions without prompts.", - operationId: "permission.allowEverything", - responses: { - 200: { - description: "Success", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400, 404), - }, - }), - validator( - "json", - z.object({ - enable: z.boolean(), - requestID: z.string().optional(), - sessionID: z.string().optional(), - }), - ), - async (c) => { - const json = c.req.valid("json") - const rules: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "allow" }] - - if (!json.enable) { - if (json.sessionID) { - const session = await Session.get(json.sessionID) - await Session.setPermission({ - sessionID: json.sessionID, - permission: (session.permission ?? []).filter( - (rule) => !(rule.permission === "*" && rule.pattern === "*" && rule.action === "allow"), - ), - }) - await PermissionNext.allowEverything({ enable: false, sessionID: json.sessionID }) - return c.json(true) - } - - await Config.updateGlobal({ permission: { "*": { "*": null } } }, { dispose: false }) - await PermissionNext.allowEverything({ enable: false }) - return c.json(true) - } - - if (json.sessionID) { - const session = await Session.get(json.sessionID) - const existing = session.permission ?? [] - await Session.setPermission({ - sessionID: json.sessionID, - permission: [...existing, ...rules], - }) - } else { - await Config.updateGlobal({ permission: PermissionNext.toConfig(rules) }, { dispose: false }) - } - - await PermissionNext.allowEverything({ - enable: true, - requestID: json.requestID, - sessionID: json.sessionID, - }) - - return c.json(true) - }, - ) - // kilocode_change end .get( "/", describeRoute({ diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 7f3b12b1171..992fef087e2 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -48,6 +48,7 @@ import { errors } from "./error" import { CommitMessageRoutes } from "./routes/commit-message" // kilocode_change import { EnhancePromptRoutes } from "./routes/enhance-prompt" // kilocode_change import { KilocodeRoutes } from "./routes/kilocode" // kilocode_change +import { PermissionKilocodeRoutes } from "../kilocode/permission/routes" // kilocode_change import { Filesystem } from "@/util/filesystem" import { QuestionRoutes } from "./routes/question" import { PermissionRoutes } from "./routes/permission" @@ -277,6 +278,7 @@ export namespace Server { .route("/experimental", ExperimentalRoutes()) .route("/session", SessionRoutes()) .route("/permission", PermissionRoutes()) + .route("/permission", PermissionKilocodeRoutes()) // kilocode_change .route("/question", QuestionRoutes()) .route("/provider", ProviderRoutes()) .route("/telemetry", TelemetryRoutes()) // kilocode_change From c9ed1097d7d8b0408ed4d966936da98ff10eaf55 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 18:51:35 +0300 Subject: [PATCH 30/33] fix(cli): skip permission prompt for same-project recall reads --- packages/opencode/src/tool/recall.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index 3bf20e54376..52697a33547 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -100,16 +100,19 @@ async function read(params: { sessionID?: string }, ctx: Tool.Context) { } // kilocode_change end - await ctx.ask({ - permission: "recall", - patterns: [session.directory], - always: [session.directory], - metadata: { - sessionID: session.id, - title: session.title, - directory: session.directory, - }, - }) + const cross = session.projectID !== Instance.project.id + if (cross) { + await ctx.ask({ + permission: "recall", + patterns: [session.directory], + always: [session.directory], + metadata: { + sessionID: session.id, + title: session.title, + directory: session.directory, + }, + }) + } const msgs = await Session.messages({ sessionID: session.id }) const lines: string[] = [ From ee5bb2fa5c68bd0c81d33a7d265dfa439d35c602 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 18:51:58 +0300 Subject: [PATCH 31/33] fix(cli): exit on git failure in annotation checker --- script/check-opencode-annotations.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/script/check-opencode-annotations.ts b/script/check-opencode-annotations.ts index 314b399d460..48370c990f6 100644 --- a/script/check-opencode-annotations.ts +++ b/script/check-opencode-annotations.ts @@ -35,6 +35,11 @@ const base = baseIdx !== -1 ? args[baseIdx + 1] : "origin/main" function run(cmd: string, args: string[]) { const result = spawnSync(cmd, args, { cwd: ROOT, encoding: "utf8" }) + if (result.status !== 0) { + const msg = result.stderr?.trim() || result.stdout?.trim() || "unknown error" + console.error(`Command failed: ${cmd} ${args.join(" ")}\n${msg}`) + process.exit(1) + } return result.stdout?.trim() ?? "" } From 3948bb3f819f68f596707bb2409f7a3c15b205ce Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 18:57:45 +0300 Subject: [PATCH 32/33] fix(cli): skip config-protected prompts in drain loop --- packages/opencode/src/permission/next.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index ddf83c3c689..ce7388788ba 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -396,6 +396,7 @@ export namespace PermissionNext { for (const [id, entry] of Object.entries(s.pending)) { if (input.sessionID && entry.info.sessionID !== input.sessionID) continue + if (ConfigProtection.isRequest(entry.info)) continue delete s.pending[id] Bus.publish(Event.Replied, { sessionID: entry.info.sessionID, From 21531ce6c6f517b9af3f634cd06477c2538d7da0 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 19:11:44 +0300 Subject: [PATCH 33/33] fix(cli): add missing kilocode_change annotations --- .../opencode/src/cli/cmd/tui/routes/session/permission.tsx | 6 +++--- packages/opencode/src/permission/next.ts | 6 +++--- packages/opencode/test/permission/next.test.ts | 2 ++ .../test/server/permission-allow-everything.test.ts | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 823d17e1836..272d308a31c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -149,7 +149,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { }) const { theme } = useTheme() - const keybind = useKeybind() + const keybind = useKeybind() // kilocode_change return ( @@ -427,13 +427,13 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {current.title} - {/* kilocode_change start - explain config file edits always require approval */} + {/* // kilocode_change start - explain config file edits always require approval */} Config file edits always require approval - {/* kilocode_change end */} + {/* // kilocode_change end */} ) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index ce7388788ba..34b2cbee759 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -179,7 +179,7 @@ export namespace PermissionNext { return { pending, approved: stored, - session: {} as Record, + session: {} as Record, // kilocode_change } }) @@ -190,12 +190,12 @@ export namespace PermissionNext { async (input) => { const s = await state() const { ruleset, ...request } = input - const local = s.session[request.sessionID] ?? [] + const local = s.session[request.sessionID] ?? [] // kilocode_change // kilocode_change start — force "ask" for config file edits const protected_ = ConfigProtection.isRequest(request) // kilocode_change end for (const pattern of request.patterns ?? []) { - const rule = evaluate(request.permission, pattern, ruleset, s.approved, local) + const rule = evaluate(request.permission, pattern, ruleset, s.approved, local) // kilocode_change log.info("evaluated", { permission: request.permission, pattern, action: rule }) if (rule.action === "deny") throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 40786c27048..dcfd3b37c00 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -606,6 +606,7 @@ test("reply - always persists approval and resolves", async () => { }) }) +// kilocode_change start test("allowEverything - session-scoped enable stays within one session", async () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({ @@ -658,6 +659,7 @@ test("allowEverything - session-scoped enable stays within one session", async ( }, }) }) +// kilocode_change end test("reply - reject cancels all pending for same session", async () => { await using tmp = await tmpdir({ git: true }) diff --git a/packages/opencode/test/server/permission-allow-everything.test.ts b/packages/opencode/test/server/permission-allow-everything.test.ts index 28386e01428..b7f39e7eba1 100644 --- a/packages/opencode/test/server/permission-allow-everything.test.ts +++ b/packages/opencode/test/server/permission-allow-everything.test.ts @@ -1,3 +1,4 @@ +// kilocode_change - new file import { describe, expect, test } from "bun:test" import { PermissionNext } from "../../src/permission/next" import { Instance } from "../../src/project/instance"