Skip to content

Commit e41a38b

Browse files
committed
refactor merge commit changes
1 parent 2c6f833 commit e41a38b

File tree

10 files changed

+130
-114
lines changed

10 files changed

+130
-114
lines changed

mobile/src/utils/slashCommandHelpers.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ describe("buildMobileCompactionPayload", () => {
5555
expect(payload.metadata.parsed).toEqual({
5656
model: "anthropic:claude-opus-4-1",
5757
maxOutputTokens: 800,
58-
continueMessage: parsed.continueMessage,
59-
resumeModel: baseOptions.model,
58+
continueMessage: {
59+
text: parsed.continueMessage,
60+
imageParts: [],
61+
model: baseOptions.model,
62+
},
6063
});
6164
expect(payload.sendOptions.model).toBe("anthropic:claude-opus-4-1");
6265
expect(payload.sendOptions.mode).toBe("compact");

mobile/src/utils/slashCommandHelpers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ export function buildMobileCompactionPayload(
5656
parsed: {
5757
model: parsed.model,
5858
maxOutputTokens: parsed.maxOutputTokens,
59-
continueMessage: parsed.continueMessage,
60-
resumeModel: baseOptions.model,
59+
continueMessage: {
60+
text: parsed.continueMessage ?? "",
61+
imageParts: [],
62+
model: baseOptions.model,
63+
},
6164
},
6265
};
6366

src/browser/components/ChatInput/index.tsx

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,39 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
479479
const isSlashCommand = normalizedCommandInput.startsWith("/");
480480
const parsed = isSlashCommand ? parseCommand(normalizedCommandInput) : null;
481481

482+
// Prepare image parts early so slash commands can access them
483+
const imageParts = imageAttachments.map((img, index) => {
484+
// Validate before sending to help with debugging
485+
if (!img.url || typeof img.url !== "string") {
486+
console.error(
487+
`Image attachment [${index}] has invalid url:`,
488+
typeof img.url,
489+
img.url?.slice(0, 50)
490+
);
491+
}
492+
if (!img.url?.startsWith("data:")) {
493+
console.error(`Image attachment [${index}] url is not a data URL:`, img.url?.slice(0, 100));
494+
}
495+
if (!img.mediaType || typeof img.mediaType !== "string") {
496+
console.error(
497+
`Image attachment [${index}] has invalid mediaType:`,
498+
typeof img.mediaType,
499+
img.mediaType
500+
);
501+
}
502+
return {
503+
url: img.url,
504+
mediaType: img.mediaType,
505+
};
506+
});
507+
482508
if (parsed) {
483509
const context: SlashCommandContext = {
484510
variant,
485511
workspaceId: variant === "workspace" ? props.workspaceId : undefined,
486512
sendMessageOptions,
487513
setInput,
514+
setImageAttachments,
488515
setIsSending,
489516
setToast,
490517
setVimEnabled,
@@ -494,6 +521,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
494521
onTruncateHistory: variant === "workspace" ? props.onTruncateHistory : undefined,
495522
onCancelEdit: variant === "workspace" ? props.onCancelEdit : undefined,
496523
editMessageId: editingMessage?.id,
524+
imageParts: imageParts.length > 0 ? imageParts : undefined,
497525
resetInputHeight: () => {
498526
if (inputRef.current) {
499527
inputRef.current.style.height = "36px";
@@ -534,32 +562,6 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
534562

535563
// Workspace variant: regular message send
536564

537-
// Prepare image parts if any
538-
const imageParts = imageAttachments.map((img, index) => {
539-
// Validate before sending to help with debugging
540-
if (!img.url || typeof img.url !== "string") {
541-
console.error(
542-
`Image attachment [${index}] has invalid url:`,
543-
typeof img.url,
544-
img.url?.slice(0, 50)
545-
);
546-
}
547-
if (!img.url?.startsWith("data:")) {
548-
console.error(`Image attachment [${index}] url is not a data URL:`, img.url?.slice(0, 100));
549-
}
550-
if (!img.mediaType || typeof img.mediaType !== "string") {
551-
console.error(
552-
`Image attachment [${index}] has invalid mediaType:`,
553-
typeof img.mediaType,
554-
img.mediaType
555-
);
556-
}
557-
return {
558-
url: img.url,
559-
mediaType: img.mediaType,
560-
};
561-
});
562-
563565
try {
564566
// Regular message - send directly via API
565567
setIsSending(true);
@@ -587,6 +589,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
587589
continueMessage: {
588590
text: messageText,
589591
imageParts,
592+
model: sendMessageOptions.model,
590593
},
591594
sendMessageOptions,
592595
});
@@ -645,7 +648,11 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
645648
} = prepareCompactionMessage({
646649
workspaceId: props.workspaceId,
647650
maxOutputTokens: parsedEditingCommand.maxOutputTokens,
648-
continueMessage: { text: parsedEditingCommand.continueMessage ?? "", imageParts },
651+
continueMessage: {
652+
text: parsedEditingCommand.continueMessage ?? "",
653+
imageParts,
654+
model: sendMessageOptions.model,
655+
},
649656
model: parsedEditingCommand.model,
650657
sendMessageOptions,
651658
});

src/browser/hooks/useResumeManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export function useResumeManager() {
177177
continueMessage: {
178178
text: lastUserMsg.compactionRequest.parsed.continueMessage?.text ?? "",
179179
imageParts: lastUserMsg.compactionRequest.parsed.continueMessage?.imageParts,
180+
model: lastUserMsg.compactionRequest.parsed.continueMessage?.model ?? options.model,
180181
},
181182
});
182183
}

src/browser/utils/chatCommands.test.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ describe("prepareCompactionMessage", () => {
103103
mode: "exec",
104104
});
105105

106-
test("embeds resumeModel from base send options", () => {
106+
test("embeds continue message model from base send options", () => {
107107
const sendMessageOptions = createBaseOptions();
108108
const { metadata } = prepareCompactionMessage({
109109
workspaceId: "ws-1",
110110
maxOutputTokens: 4096,
111-
continueMessage: "Keep building",
111+
continueMessage: { text: "Keep building" },
112112
model: "anthropic:claude-3-5-haiku",
113113
sendMessageOptions,
114114
});
@@ -118,7 +118,7 @@ describe("prepareCompactionMessage", () => {
118118
throw new Error("Expected compaction metadata");
119119
}
120120

121-
expect(metadata.parsed.resumeModel).toBe(sendMessageOptions.model);
121+
expect(metadata.parsed.continueMessage?.model).toBe(sendMessageOptions.model);
122122
});
123123

124124
test("generates correct prompt text with strict summary instructions", () => {
@@ -132,4 +132,55 @@ describe("prepareCompactionMessage", () => {
132132
expect(messageText).toContain("Focus entirely on the summary");
133133
expect(messageText).toContain("Do not suggest next steps or future actions");
134134
});
135+
136+
test("does not create continueMessage when no text or images provided", () => {
137+
const sendMessageOptions = createBaseOptions();
138+
const { metadata } = prepareCompactionMessage({
139+
workspaceId: "ws-1",
140+
maxOutputTokens: 4096,
141+
sendMessageOptions,
142+
});
143+
144+
expect(metadata.type).toBe("compaction-request");
145+
if (metadata.type !== "compaction-request") {
146+
throw new Error("Expected compaction metadata");
147+
}
148+
149+
expect(metadata.parsed.continueMessage).toBeUndefined();
150+
});
151+
152+
test("creates continueMessage when text is provided", () => {
153+
const sendMessageOptions = createBaseOptions();
154+
const { metadata } = prepareCompactionMessage({
155+
workspaceId: "ws-1",
156+
continueMessage: { text: "Continue with this" },
157+
sendMessageOptions,
158+
});
159+
160+
if (metadata.type !== "compaction-request") {
161+
throw new Error("Expected compaction metadata");
162+
}
163+
164+
expect(metadata.parsed.continueMessage).toBeDefined();
165+
expect(metadata.parsed.continueMessage?.text).toBe("Continue with this");
166+
});
167+
168+
test("creates continueMessage when images are provided without text", () => {
169+
const sendMessageOptions = createBaseOptions();
170+
const { metadata } = prepareCompactionMessage({
171+
workspaceId: "ws-1",
172+
continueMessage: {
173+
text: "",
174+
imageParts: [{ url: "data:image/png;base64,abc", mediaType: "image/png" }],
175+
},
176+
sendMessageOptions,
177+
});
178+
179+
if (metadata.type !== "compaction-request") {
180+
throw new Error("Expected compaction metadata");
181+
}
182+
183+
expect(metadata.parsed.continueMessage).toBeDefined();
184+
expect(metadata.parsed.continueMessage?.imageParts).toHaveLength(1);
185+
});
135186
});

src/browser/utils/chatCommands.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,11 +542,21 @@ export function prepareCompactionMessage(options: CompactionOptions): {
542542
const effectiveModel = resolveCompactionModel(options.model);
543543

544544
// Create compaction metadata (will be stored in user message)
545+
// Only include continueMessage if there's text or images to queue after compaction
546+
const hasText = options.continueMessage?.text;
547+
const hasImages =
548+
options.continueMessage?.imageParts && options.continueMessage.imageParts.length > 0;
545549
const compactData: CompactionRequestData = {
546550
model: effectiveModel,
547551
maxOutputTokens: options.maxOutputTokens,
548-
continueMessage: options.continueMessage,
549-
resumeModel: options.sendMessageOptions.model,
552+
continueMessage:
553+
hasText || hasImages
554+
? {
555+
text: options.continueMessage?.text ?? "",
556+
imageParts: options.continueMessage?.imageParts,
557+
model: options.continueMessage?.model ?? options.sendMessageOptions.model,
558+
}
559+
: undefined,
550560
};
551561

552562
const metadata: MuxFrontendMetadata = {
@@ -740,9 +750,14 @@ export async function handleCompactCommand(
740750
const result = await executeCompaction({
741751
workspaceId,
742752
maxOutputTokens: parsed.maxOutputTokens,
743-
continueMessage: parsed.continueMessage
744-
? { text: parsed.continueMessage, imageParts: context.imageParts }
745-
: undefined,
753+
continueMessage:
754+
parsed.continueMessage || (context.imageParts && context.imageParts.length > 0)
755+
? {
756+
text: parsed.continueMessage ?? "",
757+
imageParts: context.imageParts,
758+
model: sendMessageOptions.model,
759+
}
760+
: undefined,
746761
model: parsed.model,
747762
sendMessageOptions,
748763
editMessageId,

src/common/types/message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { ImagePart } from "./ipc";
99
export interface ContinueMessage {
1010
text: string;
1111
imageParts?: ImagePart[];
12-
model: string;
12+
model?: string;
1313
}
1414

1515
// Parsed compaction request data (shared type for consistency)

src/node/services/agentSession.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { Ok, Err } from "@/common/types/result";
2323
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
2424
import { createRuntime } from "@/node/runtime/runtimeFactory";
2525
import { MessageQueue } from "./messageQueue";
26-
import { buildContinueMessageOptions } from "./compactionContinueOptions";
26+
2727
import type { StreamEndEvent, StreamAbortEvent } from "@/common/types/stream";
2828
import { CompactionHandler } from "./compactionHandler";
2929

@@ -336,16 +336,16 @@ export class AgentSession {
336336
// If this is a compaction request with a continue message, queue it for auto-send after compaction
337337
const muxMeta = options?.muxMetadata;
338338
if (muxMeta?.type === "compaction-request" && muxMeta.parsed.continueMessage && options) {
339-
// Strip out edit-specific and compaction-specific fields so the queued message is a fresh user message
340-
const { muxMetadata, mode, editMessageId, imageParts, ...rest } = options;
341-
const baseContinueOptions: SendMessageOptions = { ...rest };
342-
const sanitizedOptions = buildContinueMessageOptions(
343-
baseContinueOptions,
344-
muxMeta.parsed.continueMessage.model
345-
);
339+
// Strip out compaction-specific fields so the queued message is a fresh user message
340+
const { muxMetadata, mode, editMessageId, imageParts, maxOutputTokens, ...rest } = options;
341+
const sanitizedOptions: SendMessageOptions = {
342+
...rest,
343+
model: muxMeta.parsed.continueMessage.model ?? rest.model,
344+
};
345+
const continueImageParts = muxMeta.parsed.continueMessage.imageParts;
346346
const continuePayload =
347-
imageParts && imageParts.length > 0
348-
? { ...sanitizedOptions, imageParts }
347+
continueImageParts && continueImageParts.length > 0
348+
? { ...sanitizedOptions, imageParts: continueImageParts }
349349
: sanitizedOptions;
350350
this.messageQueue.add(muxMeta.parsed.continueMessage.text, continuePayload);
351351
this.emitQueuedMessageChanged();

src/node/services/compactionContinueOptions.test.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/node/services/compactionContinueOptions.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)