* Create a new terminal session for a workspace
(
params: TerminalCreateParams,
runtime: Runtime,
workspacePath: string,
onData: (data: string) => void,
onExit: (exitCode: number) => void,
runtimeConfig?: RuntimeConfig,
options?: CreateSessionOptions
)
| 88 | * Create a new terminal session for a workspace |
| 89 | */ |
| 90 | async createSession( |
| 91 | params: TerminalCreateParams, |
| 92 | runtime: Runtime, |
| 93 | workspacePath: string, |
| 94 | onData: (data: string) => void, |
| 95 | onExit: (exitCode: number) => void, |
| 96 | runtimeConfig?: RuntimeConfig, |
| 97 | options?: CreateSessionOptions |
| 98 | ): Promise<TerminalSession> { |
| 99 | // Include a random suffix to avoid collisions when creating multiple sessions quickly. |
| 100 | // Collisions can cause two PTYs to appear "merged" under one sessionId. |
| 101 | const sessionId = `${params.workspaceId}-${Date.now()}-${randomUUID().slice(0, 8)}`; |
| 102 | let ptyProcess: PtyHandle | null = null; |
| 103 | let runtimeLabel: string; |
| 104 | |
| 105 | if (runtime instanceof SSHRuntime) { |
| 106 | ptyProcess = await runtime.createPtySession({ |
| 107 | workspacePath, |
| 108 | cols: params.cols, |
| 109 | rows: params.rows, |
| 110 | }); |
| 111 | runtimeLabel = "SSH"; |
| 112 | log.info(`[PTY] SSH terminal for ${sessionId}: ssh ${runtime.getConfig().host}`); |
| 113 | } else if (runtime instanceof DevcontainerRuntime) { |
| 114 | // Must check before LocalBaseRuntime since DevcontainerRuntime extends it |
| 115 | const devcontainerArgs = ["exec", "--workspace-folder", workspacePath]; |
| 116 | |
| 117 | // Include config path for non-default devcontainer.json locations |
| 118 | if (runtimeConfig?.type === "devcontainer" && runtimeConfig.configPath) { |
| 119 | devcontainerArgs.push("--config", runtimeConfig.configPath); |
| 120 | } |
| 121 | |
| 122 | // Forward the runtime's credential env (GIT_ASKPASS, CODER_*, etc.) |
| 123 | // into the terminal session. The runtime owns the policy (shareCredentials gate); |
| 124 | // ptyService just relays whatever the runtime computed. |
| 125 | const containerEnv = runtime.getContainerEnv(); |
| 126 | for (const [key, value] of Object.entries(containerEnv)) { |
| 127 | devcontainerArgs.push("--remote-env", `${key}=${value}`); |
| 128 | } |
| 129 | |
| 130 | devcontainerArgs.push("--", "/bin/sh"); |
| 131 | runtimeLabel = "Devcontainer"; |
| 132 | const logArgs = redactDevcontainerArgsForLog(devcontainerArgs); |
| 133 | log.info(`[PTY] Devcontainer terminal for ${sessionId}: devcontainer ${logArgs.join(" ")}`); |
| 134 | |
| 135 | ptyProcess = spawnPtyProcess({ |
| 136 | runtimeLabel, |
| 137 | command: "devcontainer", |
| 138 | args: devcontainerArgs, |
| 139 | cwd: workspacePath, |
| 140 | cols: params.cols, |
| 141 | rows: params.rows, |
| 142 | preferElectronBuild: false, |
| 143 | }); |
| 144 | } else if (runtime instanceof LocalBaseRuntime) { |
| 145 | try { |
| 146 | await access(workspacePath, constants.F_OK); |
| 147 | } catch { |
no test coverage detected