claude-opencode-viewer 2.1.6 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.html +15 -8
  2. package/package.json +1 -1
  3. package/server.js +38 -12
package/index.html CHANGED
@@ -504,11 +504,12 @@
504
504
  var ARROW_KEYS = ['up', 'down', 'left', 'right'];
505
505
 
506
506
  function sendKey(keyName) {
507
- var isArrowKey = ARROW_KEYS.indexOf(keyName) !== -1;
507
+ // 重新获取焦点状态(确保使用最新状态)
508
+ var isInputFocused = (inputField && document.activeElement === inputField);
508
509
 
509
510
  // 方向键特殊处理:根据焦点位置决定行为
510
- if (isArrowKey) {
511
- if (inputFocused && inputField) {
511
+ if (ARROW_KEYS.indexOf(keyName) !== -1) {
512
+ if (isInputFocused) {
512
513
  // 焦点在输入框:模拟方向键操作输入框光标
513
514
  var cursorPos = inputField.selectionStart;
514
515
  var textLen = inputField.value.length;
@@ -522,28 +523,27 @@
522
523
  return;
523
524
  } else {
524
525
  // 焦点在终端:发送方向键到终端,并收起键盘
525
- if (inputField && isMobile) {
526
+ if (isMobile) {
526
527
  inputField.blur();
527
528
  }
528
529
  }
529
530
  } else {
530
531
  // 非方向键:点击时收起键盘
531
- if (inputField && isMobile) {
532
+ if (isMobile) {
532
533
  inputField.blur();
533
534
  }
534
535
  }
535
536
 
536
537
  // Enter 键特殊处理:根据焦点位置决定行为
537
538
  if (keyName === 'enter') {
538
- if (inputFocused && inputField && inputField.value) {
539
+ if (isInputFocused && inputField && inputField.value) {
539
540
  // 焦点在输入框且有内容:只发送输入框内容,不额外发送 \r
540
541
  var value = inputField.value;
541
542
  console.log('[enter] from input:', value);
542
543
  ws.send(JSON.stringify({ type: 'input', data: value + '\r' }));
543
544
  inputField.value = '';
544
- // 让输入框失去焦点,防止终端 textarea 同时收到输入
545
545
  inputField.blur();
546
- } else if (inputFocused && inputField) {
546
+ } else if (isInputFocused && inputField) {
547
547
  // 焦点在输入框但内容为空:不发送任何内容,避免误触
548
548
  console.log('[enter] input focused but empty, ignored');
549
549
  } else {
@@ -554,6 +554,13 @@
554
554
  return;
555
555
  }
556
556
 
557
+ // Esc 键:中断终端进程(发送 ESC 字符)
558
+ if (keyName === 'esc') {
559
+ console.log('[esc] sending escape to terminal');
560
+ ws.send(JSON.stringify({ type: 'input', data: '\x1b' }));
561
+ return;
562
+ }
563
+
557
564
  // 其他按键直接发送到终端
558
565
  var key = KEY_MAP[keyName];
559
566
  if (key && ws && ws.readyState === 1 && !isTransitioning) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.1.6",
3
+ "version": "2.1.8",
4
4
  "description": "A unified terminal viewer for Claude Code and OpenCode with seamless switching",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -133,18 +133,25 @@ async function spawnProcess(mode) {
133
133
  }
134
134
  });
135
135
 
136
+ // 只在初始化时杀死旧进程,switchMode 已经处理了切换时的进程清理
136
137
  if (mode === 'claude') {
137
- if (claudeProcess?.pid) {
138
- try { claudeProcess.kill(); } catch {}
138
+ if (claudeProcess && claudeProcess !== proc && claudeProcess.pid) {
139
+ try {
140
+ console.log(`[spawnProcess] 清理旧 claude 进程 PID: ${claudeProcess.pid}`);
141
+ claudeProcess.kill();
142
+ } catch {}
139
143
  }
140
144
  claudeProcess = proc;
141
145
  } else {
142
- if (opencodeProcess?.pid) {
143
- try { opencodeProcess.kill(); } catch {}
146
+ if (opencodeProcess && opencodeProcess !== proc && opencodeProcess.pid) {
147
+ try {
148
+ console.log(`[spawnProcess] 清理旧 opencode 进程 PID: ${opencodeProcess.pid}`);
149
+ opencodeProcess.kill();
150
+ } catch {}
144
151
  }
145
152
  opencodeProcess = proc;
146
153
  }
147
-
154
+
148
155
  currentProcess = proc;
149
156
  console.log(`[claude-opencode-viewer] ${mode === 'claude' ? 'Claude Code' : 'OpenCode'} 已启动 (PID: ${proc.pid})`);
150
157
  return proc;
@@ -152,24 +159,43 @@ async function spawnProcess(mode) {
152
159
 
153
160
  async function switchMode(newMode) {
154
161
  if (newMode === currentMode) return;
155
-
162
+
163
+ console.log(`[switchMode] 从 ${currentMode} 切换到 ${newMode}`);
164
+
156
165
  // 清空所有历史输出
157
166
  outputBuffer = '';
158
-
167
+
159
168
  // 杀死旧进程
160
169
  if (currentMode === 'claude' && claudeProcess) {
161
- try { claudeProcess.kill(); } catch {}
170
+ try {
171
+ console.log(`[switchMode] 杀死 claude 进程 PID: ${claudeProcess.pid}`);
172
+ claudeProcess.kill();
173
+ } catch (e) {
174
+ console.log('[switchMode] 杀死 claude 进程失败:', e.message);
175
+ }
162
176
  claudeProcess = null;
163
177
  } else if (currentMode === 'opencode' && opencodeProcess) {
164
- try { opencodeProcess.kill(); } catch {}
178
+ try {
179
+ console.log(`[switchMode] 杀死 opencode 进程 PID: ${opencodeProcess.pid}`);
180
+ opencodeProcess.kill();
181
+ } catch (e) {
182
+ console.log('[switchMode] 杀死 opencode 进程失败:', e.message);
183
+ }
165
184
  opencodeProcess = null;
166
185
  }
167
186
  currentProcess = null;
168
-
187
+
188
+ // 等待一小段时间确保进程完全退出
189
+ await new Promise(resolve => setTimeout(resolve, 100));
190
+
169
191
  // 切换到新模式
170
192
  currentMode = newMode;
171
- await spawnProcess(newMode);
172
- console.log(`[CombinedV2] 切换到 ${newMode}`);
193
+ try {
194
+ await spawnProcess(newMode);
195
+ console.log(`[switchMode] 切换到 ${newMode} 成功`);
196
+ } catch (e) {
197
+ console.error('[switchMode] 启动新进程失败:', e.message);
198
+ }
173
199
  }
174
200
 
175
201
  function writeToPty(data) {