Skip to content

Commit 65e2bae

Browse files
committed
no-mistakes(review): await initialization in sendToExtension consistently
1 parent 20ec89d commit 65e2bae

3 files changed

Lines changed: 85 additions & 18 deletions

File tree

apps/cli/src/agent/__tests__/extension-host.test.ts

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -294,35 +294,52 @@ describe("ExtensionHost", () => {
294294
})
295295

296296
describe("sendToExtension", () => {
297-
it("should throw error when extension not ready", () => {
297+
it("should throw error when extension not ready", async () => {
298298
const host = createTestHost()
299299
const message: WebviewMessage = { type: "requestModes" }
300300

301-
expect(() => {
302-
host.sendToExtension(message)
303-
}).toThrow("You cannot send messages to the extension before it is ready")
301+
await expect(host.sendToExtension(message)).rejects.toThrow(
302+
"You cannot send messages to the extension before it is ready",
303+
)
304304
})
305305

306-
it("should emit webviewMessage event when webview is ready", () => {
306+
it("should emit webviewMessage event when webview is ready", async () => {
307307
const host = createTestHost()
308308
const emitSpy = vi.spyOn(host, "emit")
309309
const message: WebviewMessage = { type: "requestModes" }
310310

311311
host.markWebviewReady()
312312
emitSpy.mockClear() // Clear the markWebviewReady calls
313-
host.sendToExtension(message)
313+
await host.sendToExtension(message)
314314

315315
expect(emitSpy).toHaveBeenCalledWith("webviewMessage", message)
316316
})
317317

318-
it("should not throw when webview is ready", () => {
318+
it("should not throw when webview is ready", async () => {
319319
const host = createTestHost()
320320

321321
host.markWebviewReady()
322322

323-
expect(() => {
324-
host.sendToExtension({ type: "requestModes" })
325-
}).not.toThrow()
323+
await expect(host.sendToExtension({ type: "requestModes" })).resolves.not.toThrow()
324+
})
325+
326+
it("should await initialization before emitting message", async () => {
327+
const host = createTestHost()
328+
const emitSpy = vi.spyOn(host, "emit")
329+
const message: WebviewMessage = { type: "requestModes" }
330+
331+
host.markWebviewReady()
332+
emitSpy.mockClear()
333+
334+
// Send message immediately after marking ready
335+
const promise = host.sendToExtension(message)
336+
337+
// Message should not be emitted yet if initialization is still pending
338+
// In this test, initialization completes synchronously, so this is just
339+
// ensuring the await happens
340+
await promise
341+
342+
expect(emitSpy).toHaveBeenCalledWith("webviewMessage", message)
326343
})
327344
})
328345

@@ -343,6 +360,49 @@ describe("ExtensionHost", () => {
343360
})
344361
})
345362

363+
describe("initialization ordering", () => {
364+
it("should ensure all messages await initialization promise", async () => {
365+
const host = createTestHost()
366+
let initializationResolved = false
367+
let resolveInit: () => void
368+
369+
// Create a manually-resolved initialization promise
370+
const initPromise = new Promise<void>((resolve) => {
371+
resolveInit = () => {
372+
initializationResolved = true
373+
resolve()
374+
}
375+
})
376+
377+
// Override the initialization promise before marking ready
378+
const privateHost = host as unknown as { initializationPromise: Promise<void>; isReady: boolean }
379+
privateHost.isReady = true
380+
privateHost.initializationPromise = initPromise
381+
382+
const emitSpy = vi.spyOn(host, "emit")
383+
384+
// Send a message - it should wait for initialization
385+
const sendPromise = host.sendToExtension({ type: "requestModes" })
386+
387+
// Give it a tick to start processing
388+
await new Promise((resolve) => setImmediate(resolve))
389+
390+
// Message should not be emitted yet because initialization hasn't resolved
391+
expect(initializationResolved).toBe(false)
392+
expect(emitSpy).not.toHaveBeenCalledWith("webviewMessage", { type: "requestModes" })
393+
394+
// Now resolve initialization
395+
resolveInit!()
396+
397+
// Wait for the message to be sent
398+
await sendPromise
399+
400+
// Now initialization should be complete and message emitted
401+
expect(initializationResolved).toBe(true)
402+
expect(emitSpy).toHaveBeenCalledWith("webviewMessage", { type: "requestModes" })
403+
})
404+
})
405+
346406
describe("public agent state API", () => {
347407
it("should return agent state from getAgentState()", () => {
348408
const host = createTestHost()
@@ -658,6 +718,8 @@ describe("ExtensionHost", () => {
658718
const taskPromise = host.runTask("delegate this").then(() => {
659719
settled = true
660720
})
721+
// Wait for newTask message to be emitted
722+
await new Promise((resolve) => setImmediate(resolve))
661723
const rootId = (messages.find((message) => message.type === "newTask") as { taskId: string }).taskId
662724

663725
api.emit(RooCodeEventName.TaskModeSwitched, rootId, "architect")

apps/cli/src/agent/extension-host.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export interface ExtensionHostInterface extends IExtensionHost<ExtensionHostEven
124124
runTask(prompt: string, taskId?: string, configuration?: RooCodeSettings, images?: string[]): Promise<void>
125125
resumeTask(taskId: string): Promise<void>
126126
cancelTask(): Promise<void>
127-
sendToExtension(message: WebviewMessage): void
127+
sendToExtension(message: WebviewMessage): Promise<void>
128128
dispose(): Promise<void>
129129
}
130130

@@ -213,7 +213,9 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
213213

214214
// Initialize client - single source of truth for agent state (including mode).
215215
this.client = new ExtensionClient({
216-
sendMessage: (msg) => this.sendToExtension(msg),
216+
sendMessage: (msg) => {
217+
void this.sendToExtension(msg)
218+
},
217219
debug: options.debug, // Enable debug logging in the client.
218220
})
219221

@@ -230,7 +232,9 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
230232
this.askDispatcher = new AskDispatcher({
231233
outputManager: this.outputManager,
232234
promptManager: this.promptManager,
233-
sendMessage: (msg) => this.sendToExtension(msg),
235+
sendMessage: (msg) => {
236+
void this.sendToExtension(msg)
237+
},
234238
nonInteractive: options.autonomous || options.nonInteractive,
235239
exitOnError: options.exitOnError,
236240
disabled: options.disableAskHandling,
@@ -503,11 +507,12 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
503507
// Message Handling
504508
// ==========================================================================
505509

506-
public sendToExtension(message: WebviewMessage): void {
510+
public async sendToExtension(message: WebviewMessage): Promise<void> {
507511
if (!this.isReady) {
508512
throw new Error("You cannot send messages to the extension before it is ready")
509513
}
510514

515+
await this.initializationPromise
511516
this.emit("webviewMessage", message)
512517
}
513518

@@ -638,7 +643,7 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
638643
const rootTaskId = taskId ?? (this.options.autonomous ? randomUUID() : "unknown")
639644
this.rootTaskId = rootTaskId
640645
const completion = this.waitForTaskCompletion(rootTaskId)
641-
this.sendToExtension({
646+
await this.sendToExtension({
642647
type: "newTask",
643648
text: prompt,
644649
...(taskId || this.options.autonomous ? { taskId: rootTaskId } : {}),
@@ -651,7 +656,7 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
651656
public async resumeTask(taskId: string): Promise<void> {
652657
this.rootTaskId = taskId
653658
const completion = this.waitForTaskCompletion(taskId)
654-
this.sendToExtension({ type: "showTaskWithId", text: taskId })
659+
await this.sendToExtension({ type: "showTaskWithId", text: taskId })
655660
this.lastTaskResult = await completion
656661
}
657662

apps/cli/src/ui/hooks/useExtensionHost.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ export function useExtensionHost({
157157

158158
// Request initial state from extension (triggers
159159
// postStateToWebview which includes taskHistory).
160-
host.sendToExtension({ type: "requestCommands" })
161-
host.sendToExtension({ type: "requestModes" })
160+
await host.sendToExtension({ type: "requestCommands" })
161+
await host.sendToExtension({ type: "requestModes" })
162162

163163
if (requestedSessionId || continueSession) {
164164
await pWaitFor(() => hasReceivedTaskHistory, {

0 commit comments

Comments
 (0)