Skip to content

Commit 277e397

Browse files
committed
fix(mpp-vscode): fix VSCode API acquisition for webview communication
- Acquire VSCode API in HTML before React loads to avoid duplicate acquisition error - Store API on window.vscodeApi for React to use - Remove debug logging from production code All 63 tests passing. Refs #31
1 parent 6b56e8a commit 277e397

File tree

4 files changed

+83
-17
lines changed

4 files changed

+83
-17
lines changed

mpp-vscode/src/providers/chat-view.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
5252

5353
// Handle messages from webview
5454
webviewView.webview.onDidReceiveMessage(async (message) => {
55+
this.log(`Received message from webview: ${JSON.stringify(message)}`);
5556
switch (message.type) {
5657
case 'sendMessage':
5758
await this.handleUserMessage(message.content);
@@ -244,10 +245,14 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
244245
/**
245246
* Create renderer that forwards events to webview
246247
* Mirrors TuiRenderer from mpp-ui
248+
* Must implement JsCodingAgentRenderer interface including __doNotUseOrImplementIt
247249
*/
248250
private createRenderer(): any {
249251
const self = this;
250252
return {
253+
// Required by Kotlin JS export interface
254+
__doNotUseOrImplementIt: {},
255+
251256
renderIterationHeader: (current: number, max: number) => {
252257
self.postMessage({ type: 'iterationUpdate', data: { current, max } });
253258
},
@@ -294,6 +299,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
294299
self.postMessage({ type: 'responseChunk', content: `\n💡 ${advice}\n` });
295300
},
296301
renderUserConfirmationRequest: () => {},
302+
addLiveTerminal: () => {},
297303
forceStop: () => {
298304
self.postMessage({ type: 'taskComplete', data: { success: false, message: 'Stopped' } });
299305
}
@@ -310,7 +316,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
310316
return;
311317
}
312318

313-
const trimmedContent = content.trim();
319+
const trimmedContent = content?.trim();
314320
if (!trimmedContent) return;
315321

316322
// Add user message to timeline
@@ -329,19 +335,14 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
329335
this.isExecuting = true;
330336

331337
try {
332-
// Initialize agent if needed
333338
const agent = this.initializeCodingAgent();
334339
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd();
335-
336-
// Create task and execute - same pattern as AgentMode.ts
337340
const task = new KotlinCC.agent.JsAgentTask(trimmedContent, workspacePath);
338341
const result = await agent.executeTask(task);
339342

340-
// Add completion message
341343
if (result && result.message) {
342344
this.messages.push({ role: 'assistant', content: result.message });
343345
}
344-
345346
} catch (error) {
346347
const message = error instanceof Error ? error.message : String(error);
347348
this.log(`Error in chat: ${message}`);
@@ -491,14 +492,23 @@ configs:
491492
<head>
492493
<meta charset="UTF-8">
493494
<meta name="viewport" content="width=device-width, initial-scale=1.0">
494-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
495+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src ${webview.cspSource} 'unsafe-inline';">
495496
<link rel="stylesheet" href="${styleUri}">
496497
<title>AutoDev Chat</title>
498+
<script>
499+
// Acquire VSCode API ONCE and store on window BEFORE React loads
500+
(function() {
501+
if (typeof acquireVsCodeApi === 'function') {
502+
window.vscodeApi = acquireVsCodeApi();
503+
console.log('[AutoDev] VSCode API acquired and stored on window.vscodeApi');
504+
}
505+
})();
506+
</script>
497507
</head>
498508
<body>
499509
<div id="root"></div>
500-
<script nonce="${nonce}" src="${scriptUri}"></script>
501-
<script nonce="${nonce}">
510+
<script src="${scriptUri}"></script>
511+
<script>
502512
// Fallback if React bundle not loaded
503513
if (!document.getElementById('root').hasChildNodes()) {
504514
document.getElementById('root').innerHTML = \`

mpp-vscode/webview/src/App.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,26 @@
110110
background: var(--vscode-button-hoverBackground);
111111
}
112112

113+
/* Loading indicator */
114+
.loading-indicator {
115+
display: flex;
116+
align-items: center;
117+
justify-content: center;
118+
gap: 10px;
119+
padding: 20px;
120+
color: var(--vscode-descriptionForeground);
121+
font-size: 13px;
122+
}
123+
124+
.loading-spinner {
125+
width: 16px;
126+
height: 16px;
127+
border: 2px solid var(--vscode-progressBar-background, #0e639c);
128+
border-top-color: transparent;
129+
border-radius: 50%;
130+
animation: spin 0.8s linear infinite;
131+
}
132+
133+
@keyframes spin {
134+
to { transform: rotate(360deg); }
135+
}

mpp-vscode/webview/src/App.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ const App: React.FC = () => {
215215

216216
// Send message to extension
217217
const handleSend = useCallback((content: string) => {
218+
// Immediately show user message in timeline for feedback
219+
setAgentState(prev => ({
220+
...prev,
221+
isProcessing: true,
222+
timeline: [...prev.timeline, {
223+
type: 'message',
224+
timestamp: Date.now(),
225+
message: { role: 'user', content }
226+
}]
227+
}));
228+
229+
// Send to extension
218230
postMessage({ type: 'sendMessage', content });
219231
}, [postMessage]);
220232

@@ -281,6 +293,14 @@ const App: React.FC = () => {
281293
onAction={handleAction}
282294
/>
283295

296+
{/* Show loading indicator when processing */}
297+
{agentState.isProcessing && !agentState.currentStreamingContent && (
298+
<div className="loading-indicator">
299+
<div className="loading-spinner"></div>
300+
<span>Processing...</span>
301+
</div>
302+
)}
303+
284304
{/* Show Open Config button when config is needed */}
285305
{needsConfig && (
286306
<div className="config-prompt">

mpp-vscode/webview/src/hooks/useVSCode.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ interface VSCodeAPI {
77
setState: (state: unknown) => void;
88
}
99

10-
// Declare the global acquireVsCodeApi function
11-
declare function acquireVsCodeApi(): VSCodeAPI;
10+
// Declare the global VSCode API on window (acquired in HTML before React loads)
11+
declare global {
12+
interface Window {
13+
vscodeApi?: VSCodeAPI;
14+
acquireVsCodeApi?: () => VSCodeAPI;
15+
}
16+
}
1217

1318
// Message types from extension
1419
// Mirrors mpp-ui's ComposeRenderer events
@@ -49,13 +54,23 @@ let vscodeApi: VSCodeAPI | null = null;
4954

5055
function getVSCodeAPI(): VSCodeAPI | null {
5156
if (vscodeApi) return vscodeApi;
52-
53-
try {
54-
vscodeApi = acquireVsCodeApi();
57+
58+
// First, check if API was already acquired and stored on window (by HTML script)
59+
if (window.vscodeApi) {
60+
vscodeApi = window.vscodeApi;
5561
return vscodeApi;
62+
}
63+
64+
// Fallback: try to acquire it ourselves (only works if not already acquired)
65+
try {
66+
const acquireFn = window.acquireVsCodeApi;
67+
if (typeof acquireFn === 'function') {
68+
vscodeApi = acquireFn();
69+
return vscodeApi;
70+
}
71+
return null;
5672
} catch {
5773
// Running outside VSCode (e.g., in browser for development)
58-
console.warn('VSCode API not available, running in standalone mode');
5974
return null;
6075
}
6176
}
@@ -69,8 +84,6 @@ export function useVSCode() {
6984
const postMessage = useCallback((message: WebviewMessage) => {
7085
if (api) {
7186
api.postMessage(message);
72-
} else {
73-
console.log('Would send to VSCode:', message);
7487
}
7588
}, [api]);
7689

0 commit comments

Comments
 (0)