添加逻辑以防止命令过早执行,导致连接开发容器失败
Some checks failed
backend / cross (aarch64) (pull_request) Failing after 6m40s
backend / cross (arm) (pull_request) Failing after 1m32s
backend / cross (armhf) (pull_request) Failing after 31s
backend / cross (i686) (pull_request) Failing after 31s
backend / cross (mips) (pull_request) Failing after 31s
backend / cross (mips64) (pull_request) Failing after 4m49s
backend / cross (mips64el) (pull_request) Failing after 1m31s
backend / cross (mipsel) (pull_request) Failing after 30s
backend / cross (s390x) (pull_request) Failing after 30s
backend / cross (win32) (pull_request) Failing after 31s
backend / cross (x86_64) (pull_request) Failing after 10m17s

This commit is contained in:
2026-01-07 15:41:32 +08:00
parent 92b7d50817
commit 1681f9e4ef
2 changed files with 16364 additions and 16280 deletions

View File

@@ -112,6 +112,7 @@ export class Xterm {
private containerStatus = "";
private attachCommandSent = false;
private attachCommandSentAt?: number;
private ptyOutputReceived = false;
constructor(
private options: XtermOptions,
private sendCb: () => void
@@ -276,6 +277,7 @@ export class Xterm {
@bind
public connect() {
this.socket = new WebSocket(this.options.wsUrl, ['tty']);
this.ptyOutputReceived = false;
const { socket, register } = this;
socket.binaryType = 'arraybuffer';
@@ -310,6 +312,7 @@ export class Xterm {
this.doReconnect = this.reconnect;
this.initListeners();
terminal.focus();
this.tryExecuteAttachCommand();
}
@bind
@@ -340,6 +343,43 @@ export class Xterm {
}
}
@bind
private tryExecuteAttachCommand() {
console.log('[Xterm] tryExecuteAttachCommand called:', {
attachCommandSent: this.attachCommandSent,
connectStatus: this.connectStatus,
hasCommand: !!(this.postAttachCommand && this.postAttachCommand.length > 0),
socketReady: this.socket?.readyState === WebSocket.OPEN,
ptyOutputReceived: this.ptyOutputReceived
});
if (this.attachCommandSent || this.connectStatus) {
console.log('[Xterm] Skipping: command already sent or connected');
return;
}
if (!this.postAttachCommand || this.postAttachCommand.length === 0) {
console.log('[Xterm] Skipping: no command available');
return;
}
if (this.socket?.readyState !== WebSocket.OPEN) {
console.log('[Xterm] Skipping: WebSocket not ready, state:', this.socket?.readyState);
return;
}
if (!this.ptyOutputReceived) {
console.log('[Xterm] Skipping: ttyd not ready yet (waiting for first output)');
return; // Wait for TTY readiness confirm via output
}
const cmd = this.postAttachCommand[0];
if (cmd) {
console.log('[Xterm] ✅ All conditions met, executing attach command...');
this.sendData(cmd + "\n");
this.attachCommandSent = true;
this.attachCommandSentAt = Date.now();
console.log('[Xterm] Command sent at:', new Date(this.attachCommandSentAt).toISOString());
}
}
/**
* 获取 URL 查询参数
*/
@@ -393,14 +433,8 @@ export class Xterm {
}
// 执行连接容器的命令(只执行一次)
const parts = data.command.split('\n');
if (parts[0] && !this.connectStatus) {
console.log('[Xterm] Successfully loaded connection command, executing...');
this.sendData(parts[0]+"\n");
this.attachCommandSent = true;
this.attachCommandSentAt = Date.now();
}
this.postAttachCommand = parts;
this.postAttachCommand = data.command.split('\n');
this.tryExecuteAttachCommand();
})
.catch(error => {
console.error(`[Xterm] Error loading command (attempt ${retryCount + 1}/${maxRetries}):`, error);
@@ -465,48 +499,66 @@ export class Xterm {
const data = rawData.slice(1);
switch (cmd) {
case Command.OUTPUT:
if (!this.ptyOutputReceived) {
this.ptyOutputReceived = true;
console.log('[Xterm] ✅ ttyd is now ready (received first output), attempting to execute attach command');
this.tryExecuteAttachCommand();
}
const decodedData = textDecoder.decode(data);
console.log('[ttyd] output:', decodedData);
const compactOutput = decodedData.replace(/\s/g, '');
const { options } = this.getUrlParams();
if (options.get('type') === 'docker') {
// 保存host的标题
if (this.hostTitle === ''){
const pureContent = decodedData.replace(/\u001B\[[0-9;?]*[ -\/]*[@-~]/g, '').replace(/\u0007/g, '').trim();
if (this.hostTitle === '' && pureContent.length > 0){
this.hostTitle = compactOutput;
console.log('[Xterm] Host title captured:', this.hostTitle);
}
// 检测是否退出devcontainer标题等于host的标题
if (this.connectStatus && compactOutput.includes(this.hostTitle)){
if (this.connectStatus && this.hostTitle && compactOutput.includes(this.hostTitle)){
console.log('[Xterm] Detected exit to host shell');
this.connectStatus = false;
this.connectionMessageBuffer = '';
this.attachCommandSent = false;
this.attachCommandSentAt = undefined;
this.postAttachCommandStatus = false;
this.ptyOutputReceived = false; // 重置 PTY 状态
}
// 检测连接完成:监听 "Successfully connected to the devcontainer" 消息
// 这条消息是由连接命令中的 echo "$WEB_TERMINAL_HELLO" 输出的
if (!this.connectStatus) {
const sanitizedOutput = this.stripAnsi(decodedData).replace(/\r/g, '\n');
const combinedOutput = this.connectionMessageBuffer + sanitizedOutput;
const segments = combinedOutput.split(/\n/);
this.connectionMessageBuffer = segments.pop() ?? '';
const hasSuccessLine = segments.some(line => line.trim() === 'Successfully connected to the devcontainer');
if (hasSuccessLine) {
if (this.connectStatus) {
try {
this.writeFunc(data);
} catch (e) {
console.error('[Xterm] writeFunc error:', e);
}
} else {
// 未连接状态:缓冲所有输出
this.connectionMessageBuffer += decodedData;
const successMarker = 'Successfully connected to the devcontainer';
// 尝试在 buffer 中查找成功标记
const markerIndex = this.connectionMessageBuffer.indexOf(successMarker);
if (markerIndex !== -1) {
console.log('[Xterm] Connection established, flushing buffer.');
this.connectStatus = true;
this.connectionMessageBuffer = '';
this.attachCommandSentAt = undefined;
console.log('[Xterm] Connection established, enabling terminal input');
// 确保终端输入已启用
this.terminal.options.disableStdin = false;
const validOutput = this.connectionMessageBuffer.substring(markerIndex);
this.writeData(validOutput);
this.connectionMessageBuffer = '';
}
if (this.connectionMessageBuffer.length > 20000) {
console.warn('[Xterm] Buffer overflow protection. Flushing all.');
this.writeData(this.connectionMessageBuffer);
this.connectionMessageBuffer = '';
}
}
// 连接完成之前,过滤掉 docker exec 命令的标题输出ANSI 码和 docker-H 开头的输出)
if (
!(this.connectStatus === false &&
(textDecoder.decode(data).includes('\x1b') ||
textDecoder.decode(data).replace(/\s/g, '').includes('docker-H')))
){
this.writeFunc(data);
}
// 连接完成且出现容器的标题且没有执行过postAttach命令
if (this.connectStatus && compactOutput.includes(this.workdir) && !this.postAttachCommandStatus){
console.log('[Xterm] Detected workdir in output, executing postAttachCommand');