Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions src/browser/components/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1870,9 +1870,11 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
api,
workspaceId: props.workspaceId,
followUpContent: {
text: messageTextForSend,
fileParts,
reviews: reviewsData,
message: {
content: messageTextForSend,
fileParts,
reviews: reviewsData,
},
muxMetadata: skillMuxMetadata,
},
sendMessageOptions: compactionSendMessageOptions,
Expand Down Expand Up @@ -1949,9 +1951,11 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
followUpContent:
parsed.continueMessage || sendFileParts?.length || reviewsData?.length
? {
text: parsed.continueMessage ?? "",
fileParts: sendFileParts,
reviews: reviewsData,
message: {
content: parsed.continueMessage ?? "",
fileParts: sendFileParts,
reviews: reviewsData,
},
}
: undefined,
model: parsed.model,
Expand All @@ -1964,7 +1968,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
}

const { finalText: finalMessageText, metadata: reviewMetadata } = prepareUserMessageForSend(
{ text: actualMessageText, reviews: reviewsData },
{ content: actualMessageText, reviews: reviewsData },
muxMetadata
);
// When editing /compact, compactionOptions already includes the base sendMessageOptions.
Expand Down
2 changes: 1 addition & 1 deletion src/browser/components/ChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export const ChatPane: React.FC<ChatPaneProps> = (props) => {
api,
workspaceId,
sendMessageOptions: pendingSendOptions,
followUpContent: { text: "Continue" },
followUpContent: { message: { content: "Continue" } },
});
}, [api, workspaceId, pendingSendOptions, autoBackgroundOnSend]);

Expand Down
10 changes: 6 additions & 4 deletions src/browser/hooks/useCompactAndRetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ function buildFollowUpFromSource(
source: Extract<DisplayedMessage, { type: "user" }>
): CompactionFollowUpInput {
return {
text: source.content,
fileParts: source.fileParts,
reviews: source.reviews,
message: {
content: source.content,
fileParts: source.fileParts,
reviews: source.reviews,
},
muxMetadata: source.agentSkill
? buildAgentSkillMetadata({
rawCommand: source.content,
Expand Down Expand Up @@ -249,7 +251,7 @@ export function useCompactAndRetry(props: { workspaceId: string }): CompactAndRe

insertIntoChatInput(
fallbackText + (shouldAppendNewline ? "\n" : ""),
nestedFollowUp?.fileParts
nestedFollowUp?.message.fileParts
);
}

Expand Down
136 changes: 79 additions & 57 deletions src/browser/utils/chatCommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ describe("prepareCompactionMessage", () => {
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
maxOutputTokens: 4096,
followUpContent: { text: "Keep building" },
followUpContent: { message: { content: "Keep building" } },
model: "anthropic:claude-3-5-haiku",
sendMessageOptions,
});
Expand All @@ -280,9 +280,9 @@ describe("prepareCompactionMessage", () => {
}

// followUpContent includes model/agentId from sendMessageOptions (captured for follow-up)
expect(metadata.parsed.followUpContent?.text).toBe("Keep building");
expect(metadata.parsed.followUpContent?.model).toBe("anthropic:claude-3-5-sonnet");
expect(metadata.parsed.followUpContent?.agentId).toBe("exec");
expect(metadata.parsed.followUpContent?.message.content).toBe("Keep building");
expect(metadata.parsed.followUpContent?.sendOptions.model).toBe("anthropic:claude-3-5-sonnet");
expect(metadata.parsed.followUpContent?.sendOptions.agentId).toBe("exec");
});

test("does not create followUpContent when no text or images provided", () => {
Expand Down Expand Up @@ -312,7 +312,7 @@ describe("prepareCompactionMessage", () => {

const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: { text: "Continue" },
followUpContent: { message: { content: "Continue" } },
sendMessageOptions,
});

Expand All @@ -321,8 +321,8 @@ describe("prepareCompactionMessage", () => {
}

// Follow-up should use the user's original model/agentId
expect(metadata.parsed.followUpContent?.model).toBe("openai:gpt-4o");
expect(metadata.parsed.followUpContent?.agentId).toBe("code");
expect(metadata.parsed.followUpContent?.sendOptions.model).toBe("openai:gpt-4o");
expect(metadata.parsed.followUpContent?.sendOptions.agentId).toBe("code");
});

test("uses agentId from sendMessageOptions in followUpContent", () => {
Expand All @@ -335,22 +335,22 @@ describe("prepareCompactionMessage", () => {

const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: { text: "Continue" },
followUpContent: { message: { content: "Continue" } },
sendMessageOptions,
});

if (metadata.type !== "compaction-request") {
throw new Error("Expected compaction metadata");
}

expect(metadata.parsed.followUpContent?.agentId).toBe("exec");
expect(metadata.parsed.followUpContent?.sendOptions.agentId).toBe("exec");
});

test("creates followUpContent when text is provided", () => {
const sendMessageOptions = createBaseOptions();
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: { text: "Continue with this" },
followUpContent: { message: { content: "Continue with this" } },
sendMessageOptions,
});

Expand All @@ -359,7 +359,7 @@ describe("prepareCompactionMessage", () => {
}

expect(metadata.parsed.followUpContent).toBeDefined();
expect(metadata.parsed.followUpContent?.text).toBe("Continue with this");
expect(metadata.parsed.followUpContent?.message.content).toBe("Continue with this");
});

test("rawCommand includes multiline continue payload", () => {
Expand All @@ -368,7 +368,7 @@ describe("prepareCompactionMessage", () => {
workspaceId: "ws-1",
maxOutputTokens: 2048,
model: "anthropic:claude-3-5-haiku",
followUpContent: { text: "Line 1\nLine 2" },
followUpContent: { message: { content: "Line 1\nLine 2" } },
sendMessageOptions,
});

Expand All @@ -385,7 +385,7 @@ describe("prepareCompactionMessage", () => {
const sendMessageOptions = createBaseOptions();
const { messageText, metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: { text: "Continue" },
followUpContent: { message: { content: "Continue" } },
sendMessageOptions,
});

Expand All @@ -396,14 +396,14 @@ describe("prepareCompactionMessage", () => {
}

// Still queued for auto-send after compaction
expect(metadata.parsed.followUpContent?.text).toBe("Continue");
expect(metadata.parsed.followUpContent?.message.content).toBe("Continue");
});

test("includes non-default continue text in compaction prompt", () => {
const sendMessageOptions = createBaseOptions();
const { messageText } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: { text: "fix tests" },
followUpContent: { message: { content: "fix tests" } },
sendMessageOptions,
});

Expand All @@ -415,8 +415,10 @@ describe("prepareCompactionMessage", () => {
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: {
text: "",
fileParts: [{ url: "data:image/png;base64,abc", mediaType: "image/png" }],
message: {
content: "",
fileParts: [{ url: "data:image/png;base64,abc", mediaType: "image/png" }],
},
},
sendMessageOptions,
});
Expand All @@ -426,23 +428,25 @@ describe("prepareCompactionMessage", () => {
}

expect(metadata.parsed.followUpContent).toBeDefined();
expect(metadata.parsed.followUpContent?.fileParts).toHaveLength(1);
expect(metadata.parsed.followUpContent?.message.fileParts).toHaveLength(1);
});

test("creates followUpContent when reviews are provided without text", () => {
const sendMessageOptions = createBaseOptions();
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: {
text: "",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10-15",
selectedCode: "const x = 1;",
userNote: "Please fix this",
},
],
message: {
content: "",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10-15",
selectedCode: "const x = 1;",
userNote: "Please fix this",
},
],
},
},
sendMessageOptions,
});
Expand All @@ -452,24 +456,26 @@ describe("prepareCompactionMessage", () => {
}

expect(metadata.parsed.followUpContent).toBeDefined();
expect(metadata.parsed.followUpContent?.reviews).toHaveLength(1);
expect(metadata.parsed.followUpContent?.reviews?.[0].userNote).toBe("Please fix this");
expect(metadata.parsed.followUpContent?.message.reviews).toHaveLength(1);
expect(metadata.parsed.followUpContent?.message.reviews?.[0].userNote).toBe("Please fix this");
});

test("creates followUpContent with reviews and text combined", () => {
const sendMessageOptions = createBaseOptions();
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: {
text: "Also check the tests",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10-15",
selectedCode: "const x = 1;",
userNote: "Fix this bug",
},
],
message: {
content: "Also check the tests",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10-15",
selectedCode: "const x = 1;",
userNote: "Fix this bug",
},
],
},
},
sendMessageOptions,
});
Expand All @@ -479,8 +485,8 @@ describe("prepareCompactionMessage", () => {
}

expect(metadata.parsed.followUpContent).toBeDefined();
expect(metadata.parsed.followUpContent?.text).toBe("Also check the tests");
expect(metadata.parsed.followUpContent?.reviews).toHaveLength(1);
expect(metadata.parsed.followUpContent?.message.content).toBe("Also check the tests");
expect(metadata.parsed.followUpContent?.message.reviews).toHaveLength(1);
});

test("builds followUpContent from sourceContent with skill metadata", () => {
Expand All @@ -489,7 +495,9 @@ describe("prepareCompactionMessage", () => {
const { metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: {
text: "/tests run all tests",
message: {
content: "/tests run all tests",
},
muxMetadata: {
type: "agent-skill",
rawCommand: "/tests run all tests",
Expand All @@ -506,7 +514,7 @@ describe("prepareCompactionMessage", () => {

// ContinueMessage should be built from sourceContent
expect(metadata.parsed.followUpContent).toBeDefined();
expect(metadata.parsed.followUpContent?.text).toBe("/tests run all tests");
expect(metadata.parsed.followUpContent?.message.content).toBe("/tests run all tests");

// Skill metadata should be preserved in muxMetadata
expect(metadata.parsed.followUpContent?.muxMetadata).toEqual({
Expand All @@ -522,15 +530,17 @@ describe("prepareCompactionMessage", () => {
const { messageText, metadata } = prepareCompactionMessage({
workspaceId: "ws-1",
followUpContent: {
text: "Continue",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10",
selectedCode: "x = 1",
userNote: "Check this",
},
],
message: {
content: "Continue",
reviews: [
{
filePath: "src/test.ts",
lineRange: "10",
selectedCode: "x = 1",
userNote: "Check this",
},
],
},
},
sendMessageOptions,
});
Expand All @@ -543,7 +553,7 @@ describe("prepareCompactionMessage", () => {
throw new Error("Expected compaction metadata");
}

expect(metadata.parsed.followUpContent?.reviews).toHaveLength(1);
expect(metadata.parsed.followUpContent?.message.reviews).toHaveLength(1);
});
});

Expand Down Expand Up @@ -745,13 +755,19 @@ describe("handleCompactCommand", () => {
expect(sendMessageMock).toHaveBeenCalled();

const callArgs = sendMessageMock.mock.calls[0][0] as {
options?: { muxMetadata?: { parsed?: { followUpContent?: { reviews?: ReviewNoteData[] } } } };
options?: {
muxMetadata?: {
parsed?: {
followUpContent?: { message: { reviews?: ReviewNoteData[] } };
};
};
};
};
const followUpContent = callArgs?.options?.muxMetadata?.parsed?.followUpContent;

expect(followUpContent).toBeDefined();
expect(followUpContent?.reviews).toHaveLength(1);
expect(followUpContent?.reviews?.[0].userNote).toBe("Please fix this bug");
expect(followUpContent?.message.reviews).toHaveLength(1);
expect(followUpContent?.message.reviews?.[0].userNote).toBe("Please fix this bug");
});

test("creates followUpContent with only reviews (no text)", async () => {
Expand All @@ -773,12 +789,18 @@ describe("handleCompactCommand", () => {
expect(sendMessageMock).toHaveBeenCalled();

const callArgs = sendMessageMock.mock.calls[0][0] as {
options?: { muxMetadata?: { parsed?: { followUpContent?: { reviews?: ReviewNoteData[] } } } };
options?: {
muxMetadata?: {
parsed?: {
followUpContent?: { message: { reviews?: ReviewNoteData[] } };
};
};
};
};
const followUpContent = callArgs?.options?.muxMetadata?.parsed?.followUpContent;

// Should have followUpContent even without text, because reviews are present
expect(followUpContent).toBeDefined();
expect(followUpContent?.reviews).toHaveLength(1);
expect(followUpContent?.message.reviews).toHaveLength(1);
});
});
Loading