From 748fcb7ebd2df3c6b24948f5fdddeeb3fa26b867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Mon, 25 May 2026 06:47:55 +0100 Subject: [PATCH 01/38] fix(session): exclude orphaned interrupted tools from run-loop continuation (#26178) Co-authored-by: Aiden Cline --- packages/opencode/src/session/prompt.ts | 27 +++++++++++++---- packages/opencode/test/session/prompt.test.ts | 29 +++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 2fc93c482..936b0ff03 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -81,6 +81,12 @@ const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested struc const log = Log.create({ service: "session.prompt" }) const elog = EffectLogger.create({ service: "session.prompt" }) +function isOrphanedInterruptedTool(part: MessageV2.ToolPart) { + // cleanup() marks abandoned tool_use blocks this way after retries/aborts. + // They are not pending work and must not trigger an assistant-prefill request. + return part.state.status === "error" && part.state.metadata?.interrupted === true +} + export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly prompt: (input: PromptInput) => Effect.Effect @@ -1257,12 +1263,13 @@ export const layer = Layer.effect( const lastAssistantMsg = msgs.findLast( (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id, ) - // Some providers return "stop" even when the assistant message contains tool calls. - // Keep the loop running so tool results can be sent back to the model. - // Skip provider-executed tool parts — those were fully handled within the - // provider's stream (e.g. DWS Agent Platform) and don't need a re-loop. + // Some providers return "stop" even when the assistant message contains + // tool calls. Keep the loop running so tool results can be sent back to + // the model, but ignore cleanup-marked interrupted orphans. const hasToolCalls = - lastAssistantMsg?.parts.some((part) => part.type === "tool" && !part.metadata?.providerExecuted) ?? false + lastAssistantMsg?.parts.some( + (part) => part.type === "tool" && !part.metadata?.providerExecuted && !isOrphanedInterruptedTool(part), + ) ?? false if ( lastAssistant?.finish && @@ -1270,6 +1277,16 @@ export const layer = Layer.effect( !hasToolCalls && lastUser.id < lastAssistant.id ) { + const orphan = lastAssistantMsg?.parts.find( + (part): part is MessageV2.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part), + ) + if (orphan) { + yield* slog.warn("loop exit with orphaned interrupted tool", { + messageID: lastAssistant.id, + tool: orphan.tool, + callID: orphan.callID, + }) + } yield* slog.info("exiting loop") break } diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index ff9ded4d1..4c4647457 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -457,6 +457,35 @@ noLLMServer.instance( { config: cfg }, ) +it.instance("loop exits without an LLM request for interrupted orphan tool calls", () => + Effect.gen(function* () { + const { llm } = yield* useServerConfig(providerCfg) + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + const seeded = yield* seed(chat.id, { finish: "stop" }) + yield* sessions.updatePart({ + id: PartID.ascending(), + messageID: seeded.assistant.id, + sessionID: chat.id, + type: "tool", + callID: "interrupted-call", + tool: "edit", + state: { + status: "error", + input: {}, + error: "Tool execution aborted", + metadata: { interrupted: true }, + time: { start: 1, end: 2 }, + }, + }) + + const result = yield* prompt.loop({ sessionID: chat.id }) + expect(result.info.id).toBe(seeded.assistant.id) + expect(yield* llm.hits).toHaveLength(0) + }), +) + it.instance("loop calls LLM and returns assistant message", () => Effect.gen(function* () { const { llm } = yield* useServerConfig(providerCfg) From 7703786498e2d3609f649168e54919c344fe10ee Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Mon, 25 May 2026 11:42:18 +0200 Subject: [PATCH 02/38] perf: serve console from `us-east-2` instead of `us-east-1` (#28640) --- infra/console.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/console.ts b/infra/console.ts index 29e473de3..2a74c1270 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -281,7 +281,7 @@ new sst.cloudflare.x.SolidStart("Console", { }, transform: { server: { - placement: { region: "aws:us-east-1" }, + placement: { region: "aws:us-east-2" }, transform: { worker: { tailConsumers: [{ service: logProcessor.nodes.worker.scriptName }], From 0de5f1ff3616409d1c0ef9175ac264c3e2408a2f Mon Sep 17 00:00:00 2001 From: Braxton Schafer Date: Mon, 25 May 2026 09:24:19 -0500 Subject: [PATCH 03/38] feat(tui): make prompt size responsive and configurable (#28255) --- .../src/cli/cmd/tui/component/prompt/index.tsx | 10 ++++++++-- .../opencode/src/cli/cmd/tui/config/tui-schema.ts | 10 ++++++++++ packages/opencode/src/cli/cmd/tui/routes/home.tsx | 13 +++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 0566e07b3..e8affacda 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1464,11 +1464,15 @@ export function Prompt(props: PromptProps) { }), } }) + const maxHeight = createMemo( + () => tuiConfig.prompt?.max_height ?? Math.max(6, Math.floor(dimensions().height / 3)), + ) return ( <> - (anchor = r)} visible={props.visible !== false}> + (anchor = r)} visible={props.visible !== false} width="100%">