Skip to content

Commit bb3ab70

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 2015cb2 commit bb3ab70

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
@@ -562,6 +562,11 @@ export class WorkspaceService extends EventEmitter {
562562
/**
563563
* Asynchronously generates an AI workspace name and renames the workspace if successful.
564564
* This runs in the background after workspace creation to avoid blocking the UX.
565+
*
566+
* The method:
567+
* 1. Generates the AI name (can run while stream is active)
568+
* 2. Waits for any active stream to complete (rename is blocked during streaming)
569+
* 3. Attempts to rename the workspace
565570
*/
566571
private async generateAndApplyAIName(
567572
workspaceId: string,
@@ -604,6 +609,9 @@ export class WorkspaceService extends EventEmitter {
604609
return;
605610
}
606611

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

@@ -631,6 +639,42 @@ export class WorkspaceService extends EventEmitter {
631639
}
632640
}
633641

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

0 commit comments

Comments
 (0)