1- import {
2- type Workspace ,
3- type WorkspaceAgent ,
4- } from "coder/site/src/api/typesGenerated" ;
51import * as fs from "node:fs/promises" ;
62import * as os from "node:os" ;
73import * as path from "node:path" ;
@@ -13,20 +9,11 @@ import {
139 extractAgents ,
1410 workspaceStatusLabel ,
1511} from "./api/api-helper" ;
16- import { type CoderApi } from "./api/coderApi" ;
1712import * as cliExec from "./core/cliExec" ;
18- import { type CliManager } from "./core/cliManager" ;
19- import { type ServiceContainer } from "./core/container" ;
20- import { type MementoManager } from "./core/mementoManager" ;
21- import { type PathResolver } from "./core/pathResolver" ;
22- import { type SecretsManager } from "./core/secretsManager" ;
2313import { appendVsCodeLogs } from "./core/supportBundleLogs" ;
24- import { type DeploymentManager } from "./deployment/deploymentManager" ;
2514import { CertificateError } from "./error/certificateError" ;
2615import { toError } from "./error/errorUtils" ;
2716import { type FeatureSet , featureSetForVersion } from "./featureSet" ;
28- import { type Logger } from "./logging/logger" ;
29- import { type LoginCoordinator } from "./login/loginCoordinator" ;
3017import { withCancellableProgress , withProgress } from "./progress" ;
3118import { maybeAskAgent , maybeAskUrl } from "./promptUtils" ;
3219import {
@@ -36,13 +23,31 @@ import {
3623import { resolveCliAuth } from "./settings/cli" ;
3724import { toRemoteAuthority , toSafeHost } from "./util" ;
3825import { vscodeProposed } from "./vscodeProposed" ;
39- import { type PongMessage , type WindowIpc } from "./windowIpc" ;
4026import {
4127 AgentTreeItem ,
4228 type OpenableTreeItem ,
4329 WorkspaceTreeItem ,
4430} from "./workspace/workspacesProvider" ;
4531
32+ import type {
33+ Workspace ,
34+ WorkspaceAgent ,
35+ } from "coder/site/src/api/typesGenerated" ;
36+
37+ import type { CoderApi } from "./api/coderApi" ;
38+ import type { CliManager } from "./core/cliManager" ;
39+ import type { ServiceContainer } from "./core/container" ;
40+ import type { MementoManager } from "./core/mementoManager" ;
41+ import type { PathResolver } from "./core/pathResolver" ;
42+ import type { SecretsManager } from "./core/secretsManager" ;
43+ import type { DeploymentManager } from "./deployment/deploymentManager" ;
44+ import type { Logger } from "./logging/logger" ;
45+ import type { LoginCoordinator } from "./login/loginCoordinator" ;
46+ import type {
47+ DuplicateWorkspaceIpc ,
48+ PongMessage ,
49+ } from "./workspace/duplicateWorkspaceIpc" ;
50+
4651interface OpenOptions {
4752 workspaceOwner ?: string ;
4853 workspaceName ?: string ;
@@ -66,7 +71,7 @@ export class Commands {
6671 private readonly secretsManager : SecretsManager ;
6772 private readonly cliManager : CliManager ;
6873 private readonly loginCoordinator : LoginCoordinator ;
69- private readonly windowIpc : WindowIpc ;
74+ private readonly duplicateWorkspaceIpc : DuplicateWorkspaceIpc ;
7075
7176 // These will only be populated when actively connected to a workspace and are
7277 // used in commands. Because commands can be executed by the user, it is not
@@ -90,7 +95,7 @@ export class Commands {
9095 this . secretsManager = serviceContainer . getSecretsManager ( ) ;
9196 this . cliManager = serviceContainer . getCliManager ( ) ;
9297 this . loginCoordinator = serviceContainer . getLoginCoordinator ( ) ;
93- this . windowIpc = serviceContainer . getWindowIpc ( ) ;
98+ this . duplicateWorkspaceIpc = serviceContainer . getDuplicateWorkspaceIpc ( ) ;
9499 }
95100
96101 /**
@@ -1083,8 +1088,9 @@ export class Commands {
10831088 // Only set the memento when opening a new folder/window
10841089 await this . mementoManager . setStartupMode ( "start" ) ;
10851090
1086- // Best-effort: detect other connected windows in the background.
1087- this . windowIpc
1091+ // Best-effort check for an already-connected window. Runs in the
1092+ // background so it never delays openFolder.
1093+ this . duplicateWorkspaceIpc
10881094 . sendPing ( remoteAuthority )
10891095 . then ( ( pong ) => {
10901096 if ( pong ) {
@@ -1117,10 +1123,8 @@ export class Commands {
11171123 return true ;
11181124 }
11191125
1120- /**
1121- * Non-blocking notification — VS Code may dismiss it at any time,
1122- * so this must not be awaited.
1123- */
1126+ // VS Code may dismiss a non-modal info message without resolving the
1127+ // thenable, so this must not be awaited from the open path.
11241128 private showMultiWindowNotification (
11251129 pong : PongMessage ,
11261130 remoteAuthority : string ,
@@ -1129,13 +1133,13 @@ export class Commands {
11291133 const openEmptyAction = "Open Without Folder" ;
11301134 vscode . window
11311135 . showInformationMessage (
1132- `A window is already connected to this workspace ( ${ pong . folder } ).` ,
1136+ "This workspace is already open in another window." ,
11331137 duplicateAction ,
11341138 openEmptyAction ,
11351139 )
11361140 . then ( async ( choice ) => {
11371141 if ( choice === duplicateAction ) {
1138- await this . windowIpc . sendDuplicate ( pong . sessionId ) ;
1142+ await this . duplicateWorkspaceIpc . sendDuplicate ( pong . sessionId ) ;
11391143 } else if ( choice === openEmptyAction ) {
11401144 await vscode . commands . executeCommand ( "vscode.newWindow" , {
11411145 remoteAuthority,
0 commit comments