From 6e5cbe6b08b74facc147a898a2c00e7c8539175a Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 7 Nov 2026 17:57:25 +0000 Subject: [PATCH] fix(client-runtime): removing every context item from a queued message edit now clears it A queued message edit that removed every context item sent no `context`, which the server reads as "keep", so the removed context still reached the agent. `editQueuedRun` now always sends `context` with an attachment edit, falling back to empty records, as web already does; text-only edits still omit it. Co-Authored-By: Claude Opus 5.5 --- .../src/operations/commands.test.ts | 36 +++++++++++++++++++ .../client-runtime/src/operations/commands.ts | 12 +++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/client-runtime/src/operations/commands.test.ts b/packages/client-runtime/src/operations/commands.test.ts index 821a92721d357..22cf1bccee28f 100644 --- a/packages/client-runtime/src/operations/commands.test.ts +++ b/packages/client-runtime/src/operations/commands.test.ts @@ -37,6 +37,7 @@ import type { WsRpcProtocolClient } from "../rpc/protocol.ts"; import { v2Now, v2Projection, v2ThreadId } from "../state/orchestrationV2TestFixtures.ts"; import { archiveThread, + editQueuedRun, interruptThreadTurn, revertThreadCheckpoint, resumeThreadQueue, @@ -153,6 +154,41 @@ describe("V2 environment commands", () => { }).pipe(Effect.provide(layerTestRandom)), ); + it.effect("clears queued message context when an edit removes every context item", () => + Effect.gen(function* () { + const commands: OrchestrationV2Command[] = []; + const supervisor = yield* makeSupervisor({ commands, projects: [] }); + const threadId = ThreadId.make("thread-1"); + const runId = RunId.make("run-1"); + yield* editQueuedRun({ + commandId: CommandId.make("edit-cleared"), + threadId, + runId, + text: "edited", + edit: { messageId: MessageId.make("message-1"), attachments: [] }, + }).pipe(Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor)); + yield* editQueuedRun({ + commandId: CommandId.make("edit-text"), + threadId, + runId, + text: "text only", + }).pipe(Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor)); + + expect(commands).toEqual([ + { + type: "queued-run.edit", + commandId: "edit-cleared", + threadId, + runId, + text: "edited", + attachments: [], + context: { version: 1, records: [] }, + }, + { type: "queued-run.edit", commandId: "edit-text", threadId, runId, text: "text only" }, + ]); + }).pipe(Effect.provide(layerTestRandom)), + ); + it.effect("preserves caller command ids for idempotent V2 commands", () => Effect.gen(function* () { const commands: OrchestrationV2Command[] = []; diff --git a/packages/client-runtime/src/operations/commands.ts b/packages/client-runtime/src/operations/commands.ts index f813ece95dc45..c282a9a87cd30 100644 --- a/packages/client-runtime/src/operations/commands.ts +++ b/packages/client-runtime/src/operations/commands.ts @@ -1085,16 +1085,16 @@ export const editQueuedRun = Effect.fn("EnvironmentCommands.editQueuedRun")(func threadId: input.threadId, runId: input.runId, text: input.text, - ...(attachments === undefined ? {} : { attachments }), - ...(input.edit?.context && attachments - ? { + ...(input.edit === undefined || attachments === undefined + ? {} + : { + attachments, context: remapComposerContextAttachments( input.edit.context, input.edit.attachments, attachments, - ), - } - : {}), + ) ?? { version: 1, records: [] }, + }), }); });