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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ vscode/.metadata
# Miscellaneous
.vscode/*.aider*
.vscode/settings.json
vscode/.vscode-test
vscode/org.eclipse*

# Allow PNG files in vscode/resources
!vscode/resources/*.png
Expand Down
20 changes: 20 additions & 0 deletions vscode/core/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,25 @@ class VsCodeExtension {
return data;
};

// Selective state mutator for chat messages only
const mutateChatMessages = (
recipe: (draft: ExtensionData) => void,
): Immutable<ExtensionData> => {
const data = produce(getData(), recipe);
setData(data);

// Send only chat messages to webview instead of full state
this.state.webviewProviders.forEach((provider) => {
provider.sendMessageToWebview({
type: "CHAT_MESSAGES_UPDATE",
chatMessages: data.chatMessages,
timestamp: new Date().toISOString(),
});
});

return data;
};
Comment on lines +119 to +136
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate state propagation defeats message bloat reduction.

The mutateChatMessages function calls setData(data) on line 124, which fires the _onDidChange event. The existing listeners on lines 676-680 are subscribed to this event and send the FULL ExtensionData to all webviews:

this.onDidChangeData((data) => {
  provider.sendMessageToWebview(data);  // Sends full state including all chatMessages
})

Then lines 127-133 send the selective CHAT_MESSAGES_UPDATE. This means webviews receive:

  1. Full ExtensionData (including the complete chatMessages array)
  2. Selective CHAT_MESSAGES_UPDATE (just the chatMessages)

This sends MORE data than before, not less, defeating the PR's goal of addressing message bloat.

Solution: Consider one of these approaches:

  1. Introduce a separate event emitter for chat-only changes that doesn't trigger full state propagation
  2. Modify mutateChatMessages to update internal state without firing onDidChange
  3. Add a flag to distinguish between full state changes and chat-only changes, and filter in the listeners
🤖 Prompt for AI Agents
In vscode/core/src/extension.ts around lines 119-136, calling setData(data)
inside mutateChatMessages triggers the global onDidChangeData listeners (sending
the full ExtensionData) and then you also send a CHAT_MESSAGES_UPDATE, causing
duplicate/full-state propagation and negating the message-bloat fix; fix by
preventing the global onDidChangeData event from firing for chat-only updates:
add a dedicated chat-only EventEmitter (e.g., onChatMessagesChange) and have
mutateChatMessages update the in-memory data without invoking the global emitter
(either by using a setDataSilent internal helper or by passing an options flag
to setData to suppress the global event), then emit only the new chat-only event
to notify webviews; update existing global onDidChangeData listeners to remain
unchanged and replace chat-specific webview sends to subscribe to the new chat
emitter so webviews receive only the selective CHAT_MESSAGES_UPDATE.


const taskManager = new DiagnosticTaskManager(getExcludedDiagnosticSources());

this.state = {
Expand All @@ -132,6 +151,7 @@ class VsCodeExtension {
return getData();
},
mutateData,
mutateChatMessages,
modifiedFiles: new Map(),
modifiedFilesEventEmitter: new EventEmitter(),
lastMessageId: "0",
Expand Down
1 change: 1 addition & 0 deletions vscode/core/src/extensionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface ExtensionState {
issueModel: IssuesModel;
data: Immutable<ExtensionData>;
mutateData: (recipe: (draft: ExtensionData) => void) => Immutable<ExtensionData>;
mutateChatMessages: (recipe: (draft: ExtensionData) => void) => Immutable<ExtensionData>;
profiles?: AnalysisProfile[];
activeProfileId?: string;
kaiFsCache: InMemoryCacheWithRevisions<string, string>;
Expand Down
6 changes: 3 additions & 3 deletions vscode/core/src/utilities/ModifiedFiles/processMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export const processMessageByType = async (
switch (msg.type) {
case KaiWorkflowMessageType.ToolCall: {
// Add or update tool call notification in chat
state.mutateData((draft) => {
state.mutateChatMessages((draft) => {
const toolName = msg.data.name || "unnamed tool";
const toolStatus = msg.data.status;
// Check if the most recent message is a tool message with the same name
Expand Down Expand Up @@ -292,7 +292,7 @@ export const processMessageByType = async (

if (msg.id !== state.lastMessageId) {
// This is a new message - create a new chat message
state.mutateData((draft) => {
state.mutateChatMessages((draft) => {
draft.chatMessages.push({
kind: ChatMessageType.String,
messageToken: msg.id,
Expand All @@ -305,7 +305,7 @@ export const processMessageByType = async (
state.lastMessageId = msg.id;
} else {
// This is a continuation of the current message - append to it
state.mutateData((draft) => {
state.mutateChatMessages((draft) => {
if (draft.chatMessages.length > 0) {
draft.chatMessages[draft.chatMessages.length - 1].value.message += content;
} else {
Expand Down
4 changes: 2 additions & 2 deletions webview-ui/src/components/ViolationIncidentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const ViolationIncidentsList = ({
groupId: string;
}) => {
const successRate = getSuccessRate(incidents);
console.log("successRate", successRate);
// Removed console.log for performance

if (!successRate) {
return null;
Expand Down Expand Up @@ -202,7 +202,7 @@ const ViolationIncidentsList = ({
if (filters.hasSuccessRate) {
filtered = filtered.filter((incident) => {
const successRate = extractSuccessRateData(incident.successRateMetric);
console.log("Filtering incident:", incident.violationId, "successRate:", successRate);
// Removed console.log for performance
return (
successRate && (successRate.accepted_solutions > 0 || successRate.rejected_solutions > 0)
);
Expand Down
32 changes: 21 additions & 11 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,30 @@
const [state, setState] = useState<ExtensionData>(windowState);

useEffect(() => {
const handleMessage = (event: MessageEvent<ExtensionData>) => {
// Ensure incoming data has all required array properties
const handleMessage = (event: MessageEvent<ExtensionData | { type: string; chatMessages: any[]; timestamp: string }>) => {

Check warning on line 70 in webview-ui/src/context/ExtensionStateContext.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `event:·MessageEvent<ExtensionData·|·{·type:·string;·chatMessages:·any[];·timestamp:·string·}>` with `⏎······event:·MessageEvent<ExtensionData·|·{·type:·string;·chatMessages:·any[];·timestamp:·string·}>,⏎····`
// Handle selective chat message updates
if ('type' in event.data && event.data.type === 'CHAT_MESSAGES_UPDATE') {

Check warning on line 72 in webview-ui/src/context/ExtensionStateContext.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `'type'·in·event.data·&&·event.data.type·===·'CHAT_MESSAGES_UPDATE'` with `"type"·in·event.data·&&·event.data.type·===·"CHAT_MESSAGES_UPDATE"`
setState(prevState => ({

Check warning on line 73 in webview-ui/src/context/ExtensionStateContext.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `prevState` with `(prevState)`
...prevState,
chatMessages: Array.isArray(event.data.chatMessages) ? event.data.chatMessages : prevState.chatMessages,

Check warning on line 75 in webview-ui/src/context/ExtensionStateContext.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `·?·event.data.chatMessages` with `⏎············?·event.data.chatMessages⏎···········`
}));
return;
}

// Handle full state updates (for non-chat changes)
const data = event.data as ExtensionData;
const safeData: ExtensionData = {
...defaultState,
...event.data,
ruleSets: Array.isArray(event.data.ruleSets) ? event.data.ruleSets : [],
enhancedIncidents: Array.isArray(event.data.enhancedIncidents)
? event.data.enhancedIncidents
...data,
ruleSets: Array.isArray(data.ruleSets) ? data.ruleSets : [],
enhancedIncidents: Array.isArray(data.enhancedIncidents)

Check warning on line 86 in webview-ui/src/context/ExtensionStateContext.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎··········?·data.enhancedIncidents⏎·········` with `·?·data.enhancedIncidents`
? data.enhancedIncidents
: [],
chatMessages: Array.isArray(event.data.chatMessages) ? event.data.chatMessages : [],
configErrors: Array.isArray(event.data.configErrors) ? event.data.configErrors : [],
profiles: Array.isArray(event.data.profiles) ? event.data.profiles : [],
activeDecorators: event.data.activeDecorators || {},
isWaitingForUserInteraction: event.data.isWaitingForUserInteraction || false,
chatMessages: Array.isArray(data.chatMessages) ? data.chatMessages : [],
configErrors: Array.isArray(data.configErrors) ? data.configErrors : [],
profiles: Array.isArray(data.profiles) ? data.profiles : [],
activeDecorators: data.activeDecorators || {},
isWaitingForUserInteraction: data.isWaitingForUserInteraction || false,
};
setState(safeData);
};
Expand Down
Loading