From 785a3c06ff00902bf8b48884d3f12802f9259897 Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 10 Oct 2025 15:00:21 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Change=20interrupt=20sentinel=20?= =?UTF-8?q?from=20[INTERRUPTED]=20to=20[CONTINUE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The [CONTINUE] sentinel is clearer to models, indicating they should continue their previous work rather than viewing it as an error state. This is a quick UX improvement while we work on more comprehensive handling of interrupted messages with thinking. --- src/services/aiService.ts | 2 +- src/services/ipcMain.ts | 2 +- src/types/message.ts | 2 +- src/utils/messages/modelMessageTransform.test.ts | 8 ++++---- src/utils/messages/modelMessageTransform.ts | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/services/aiService.ts b/src/services/aiService.ts index d5df2242e..21a4a0e62 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -440,7 +440,7 @@ export class AIService extends EventEmitter { log.debug("Keeping reasoning parts for OpenAI (fetch wrapper handles item_references)"); } - // Add [INTERRUPTED] sentinel to partial messages (for model context) + // Add [CONTINUE] sentinel to partial messages (for model context) const messagesWithSentinel = addInterruptedSentinel(filteredMessages); // Convert CmuxMessage to ModelMessage format using Vercel AI SDK utility diff --git a/src/services/ipcMain.ts b/src/services/ipcMain.ts index a654af266..04ff184ac 100644 --- a/src/services/ipcMain.ts +++ b/src/services/ipcMain.ts @@ -1055,7 +1055,7 @@ export class IpcMain { // so that stream-end can properly clean up the streaming indicator this.aiService.replayStream(workspaceId); } else if (partial) { - // No active stream but there's a partial - send as regular message (shows INTERRUPTED) + // No active stream but there's a partial - send as regular message (shows CONTINUE) this.mainWindow?.webContents.send(chatChannel, partial); } } diff --git a/src/types/message.ts b/src/types/message.ts index a83dc8a75..cb0a87735 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -13,7 +13,7 @@ export interface CmuxMetadata { providerMetadata?: Record; // Raw AI SDK provider data systemMessageTokens?: number; // Token count for system message sent with this request (calculated by AIService) partial?: boolean; // Whether this message was interrupted and is incomplete - synthetic?: boolean; // Whether this message was synthetically generated (e.g., [INTERRUPTED] sentinel) + synthetic?: boolean; // Whether this message was synthetically generated (e.g., [CONTINUE] sentinel) error?: string; // Error message if stream failed errorType?: StreamErrorType; // Error type/category if stream failed compacted?: boolean; // Whether this message is a compacted summary of previous history diff --git a/src/utils/messages/modelMessageTransform.test.ts b/src/utils/messages/modelMessageTransform.test.ts index 0db2f0574..4bf51e2a4 100644 --- a/src/utils/messages/modelMessageTransform.test.ts +++ b/src/utils/messages/modelMessageTransform.test.ts @@ -420,13 +420,13 @@ describe("modelMessageTransform", () => { const result = addInterruptedSentinel(messages); - // Should have 3 messages: user, assistant, [INTERRUPTED] user + // Should have 3 messages: user, assistant, [CONTINUE] user expect(result).toHaveLength(3); expect(result[0].id).toBe("user-1"); expect(result[1].id).toBe("assistant-1"); expect(result[2].id).toBe("interrupted-assistant-1"); expect(result[2].role).toBe("user"); - expect(result[2].parts).toEqual([{ type: "text", text: "[INTERRUPTED]" }]); + expect(result[2].parts).toEqual([{ type: "text", text: "[CONTINUE]" }]); expect(result[2].metadata?.synthetic).toBe(true); expect(result[2].metadata?.timestamp).toBe(2000); }); @@ -472,10 +472,10 @@ describe("modelMessageTransform", () => { const result = addInterruptedSentinel(messages); - // Should have 3 messages: user, assistant (reasoning only), [INTERRUPTED] user + // Should have 3 messages: user, assistant (reasoning only), [CONTINUE] user expect(result).toHaveLength(3); expect(result[2].role).toBe("user"); - expect(result[2].parts).toEqual([{ type: "text", text: "[INTERRUPTED]" }]); + expect(result[2].parts).toEqual([{ type: "text", text: "[CONTINUE]" }]); }); it("should handle multiple partial messages", () => { diff --git a/src/utils/messages/modelMessageTransform.ts b/src/utils/messages/modelMessageTransform.ts index e6b9c343d..e0542e868 100644 --- a/src/utils/messages/modelMessageTransform.ts +++ b/src/utils/messages/modelMessageTransform.ts @@ -62,8 +62,8 @@ export function stripReasoningForOpenAI(messages: CmuxMessage[]): CmuxMessage[] } /** - * Add [INTERRUPTED] sentinel to partial messages by inserting a user message. - * This helps the model understand that a message was interrupted and incomplete. + * Add [CONTINUE] sentinel to partial messages by inserting a user message. + * This helps the model understand that a message was interrupted and to continue. * The sentinel is ONLY for model context, not shown in UI. * * We insert a separate user message instead of modifying the assistant message @@ -77,12 +77,12 @@ export function addInterruptedSentinel(messages: CmuxMessage[]): CmuxMessage[] { for (const msg of messages) { result.push(msg); - // If this is a partial assistant message, insert [INTERRUPTED] user message after it + // If this is a partial assistant message, insert [CONTINUE] user message after it if (msg.role === "assistant" && msg.metadata?.partial) { result.push({ id: `interrupted-${msg.id}`, role: "user", - parts: [{ type: "text", text: "[INTERRUPTED]" }], + parts: [{ type: "text", text: "[CONTINUE]" }], metadata: { timestamp: msg.metadata.timestamp, // Mark as synthetic so it can be identified if needed