Skip to content

Commit 4abbba5

Browse files
committed
fix: wait for stream completion before renaming workspace
Address review feedback: the rename() method rejects while a stream is active, so the async name generation needs to wait for the stream to complete before attempting the rename. Added waitForStreamComplete() helper that: - Returns immediately if no stream is active - Subscribes to stream-end and stream-abort events - Resolves when the workspace's stream completes This ensures the AI-generated name is applied after the first message stream finishes, rather than being silently rejected.
1 parent f085455 commit 4abbba5

File tree

1 file changed

+44
-0
lines changed

1 file changed

+44
-0
lines changed

src/node/services/workspaceService.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,11 @@ export class WorkspaceService extends EventEmitter {
563563
/**
564564
* Asynchronously generates an AI workspace name and renames the workspace if successful.
565565
* This runs in the background after workspace creation to avoid blocking the UX.
566+
*
567+
* The method:
568+
* 1. Generates the AI name (can run while stream is active)
569+
* 2. Waits for any active stream to complete (rename is blocked during streaming)
570+
* 3. Attempts to rename the workspace
566571
*/
567572
private async generateAndApplyAIName(
568573
workspaceId: string,
@@ -605,6 +610,9 @@ export class WorkspaceService extends EventEmitter {
605610
return;
606611
}
607612

613+
// Wait for the stream to complete before renaming (rename is blocked during streaming)
614+
await this.waitForStreamComplete(workspaceId);
615+
608616
// Attempt to rename the workspace
609617
const renameResult = await this.rename(workspaceId, aiGeneratedName);
610618

@@ -632,6 +640,42 @@ export class WorkspaceService extends EventEmitter {
632640
}
633641
}
634642

643+
/**
644+
* Waits for an active stream on the workspace to complete.
645+
* Returns immediately if no stream is active.
646+
*/
647+
private waitForStreamComplete(workspaceId: string): Promise<void> {
648+
// If not currently streaming, resolve immediately
649+
if (!this.aiService.isStreaming(workspaceId)) {
650+
return Promise.resolve();
651+
}
652+
653+
log.debug("Waiting for stream to complete before rename", { workspaceId });
654+
655+
return new Promise((resolve) => {
656+
// Create handler that checks for this workspace's stream end
657+
const handler = (event: StreamEndEvent | StreamAbortEvent) => {
658+
if (event.workspaceId === workspaceId) {
659+
this.aiService.off("stream-end", handler);
660+
this.aiService.off("stream-abort", handler);
661+
log.debug("Stream completed, proceeding with rename", { workspaceId });
662+
resolve();
663+
}
664+
};
665+
666+
// Listen for both normal completion and abort
667+
this.aiService.on("stream-end", handler);
668+
this.aiService.on("stream-abort", handler);
669+
670+
// Safety check: if stream already ended between the isStreaming check and subscribing
671+
if (!this.aiService.isStreaming(workspaceId)) {
672+
this.aiService.off("stream-end", handler);
673+
this.aiService.off("stream-abort", handler);
674+
resolve();
675+
}
676+
});
677+
}
678+
635679
async remove(workspaceId: string, force = false): Promise<Result<void>> {
636680
// Try to remove from runtime (filesystem)
637681
try {

0 commit comments

Comments
 (0)