diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 14a85e1d23a..2bc8812663b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1329,13 +1329,18 @@ NOTE: At any point in time through this workflow you should feel free to ask the const lastAssistant = (sessionID: SessionID) => Effect.promise(async () => { - let latest: MessageV2.WithParts | undefined - for await (const item of MessageV2.stream(sessionID)) { - latest ??= item - if (item.info.role !== "user") return item + // kilocode_change start — retry when cancel races before shellImpl writes messages + for (let attempt = 0; attempt < 10; attempt++) { + let latest: MessageV2.WithParts | undefined + for await (const item of MessageV2.stream(sessionID)) { + latest ??= item + if (item.info.role !== "user") return item + } + if (latest) return latest + await new Promise((r) => setTimeout(r, 50)) } - if (latest) return latest throw new Error("Impossible") + // kilocode_change end }) // kilocode_change — mutable close-reason per session, set by runLoop and read by loop diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 1d51f07ec6a..c52d895c710 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1619,48 +1619,61 @@ test("merges legacy tools with existing permission config", async () => { }) }) +// kilocode_change start — isolate from global config to prevent cross-test contamination +// (migrateBashPermission may write permission.bash to a global config file created by other +// test files running in parallel, which mergeDeep then prepends to the project permission keys) test("permission config preserves key order", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "kilo.json"), - JSON.stringify({ - $schema: "https://app.kilo.ai/config.json", - permission: { - "*": "deny", - edit: "ask", - write: "ask", - external_directory: "ask", - read: "allow", - todowrite: "allow", - "thoughts_*": "allow", - "reasoning_model_*": "allow", - "tools_*": "allow", - "pr_comments_*": "allow", - }, - }), - ) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const config = await Config.get() - expect(Object.keys(config.permission!)).toEqual([ - "*", - "edit", - "write", - "external_directory", - "read", - "todowrite", - "thoughts_*", - "reasoning_model_*", - "tools_*", - "pr_comments_*", - ]) - }, - }) + await using globalTmp = await tmpdir() + const prev = Global.Path.config + ;(Global.Path as { config: string }).config = globalTmp.path + await Config.invalidate(true) + try { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "kilo.json"), + JSON.stringify({ + $schema: "https://app.kilo.ai/config.json", + permission: { + "*": "deny", + edit: "ask", + write: "ask", + external_directory: "ask", + read: "allow", + todowrite: "allow", + "thoughts_*": "allow", + "reasoning_model_*": "allow", + "tools_*": "allow", + "pr_comments_*": "allow", + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(Object.keys(config.permission!)).toEqual([ + "*", + "edit", + "write", + "external_directory", + "read", + "todowrite", + "thoughts_*", + "reasoning_model_*", + "tools_*", + "pr_comments_*", + ]) + }, + }) + } finally { + ;(Global.Path as { config: string }).config = prev + await Config.invalidate(true) + } }) +// kilocode_change end // MCP config merging tests