claude-opencode-viewer 2.6.0 → 2.6.2
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.
- package/.claude/settings.local.json +2 -1
- package/index.html +170 -56
- package/package.json +1 -1
- package/server.js +36 -0
package/index.html
CHANGED
|
@@ -373,6 +373,33 @@
|
|
|
373
373
|
font-size: 12px;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
.session-delete-btn {
|
|
377
|
+
flex-shrink: 0;
|
|
378
|
+
width: 28px;
|
|
379
|
+
height: 28px;
|
|
380
|
+
border: none;
|
|
381
|
+
background: none;
|
|
382
|
+
color: #f85149;
|
|
383
|
+
font-size: 14px;
|
|
384
|
+
cursor: pointer;
|
|
385
|
+
border-radius: 50%;
|
|
386
|
+
display: flex;
|
|
387
|
+
align-items: center;
|
|
388
|
+
justify-content: center;
|
|
389
|
+
transition: all 0.15s;
|
|
390
|
+
-webkit-tap-highlight-color: transparent;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.session-delete-btn:hover {
|
|
394
|
+
background: rgba(248, 81, 73, 0.15);
|
|
395
|
+
color: #f85149;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.session-delete-btn:active {
|
|
399
|
+
background: rgba(248, 81, 73, 0.3);
|
|
400
|
+
color: #f85149;
|
|
401
|
+
}
|
|
402
|
+
|
|
376
403
|
.session-restore-hint {
|
|
377
404
|
margin-top: 8px;
|
|
378
405
|
padding: 8px 12px;
|
|
@@ -949,9 +976,6 @@
|
|
|
949
976
|
<div class="virtual-key" data-key="tab">Tab</div>
|
|
950
977
|
<div class="virtual-key" data-key="esc">Esc</div>
|
|
951
978
|
<div class="virtual-key" data-key="ctrlc">Ctrl+C</div>
|
|
952
|
-
<div class="virtual-key scroll-key" data-scroll="-5">⇡ 滚动</div>
|
|
953
|
-
<div class="virtual-key scroll-key" data-scroll="5">⇣ 滚动</div>
|
|
954
|
-
<div class="virtual-key" id="btn-copy">复制</div>
|
|
955
979
|
</div>
|
|
956
980
|
</div>
|
|
957
981
|
</div>
|
|
@@ -986,6 +1010,32 @@
|
|
|
986
1010
|
|
|
987
1011
|
term.open(document.getElementById('terminal'));
|
|
988
1012
|
|
|
1013
|
+
// OSC 52 剪贴板支持:拦截应用发送的剪贴板设置请求
|
|
1014
|
+
term.parser.registerOscHandler(52, function(data) {
|
|
1015
|
+
var idx = data.indexOf(';');
|
|
1016
|
+
if (idx === -1) return false;
|
|
1017
|
+
var b64 = data.substring(idx + 1);
|
|
1018
|
+
if (!b64 || b64 === '?') return false;
|
|
1019
|
+
try {
|
|
1020
|
+
var text = atob(b64);
|
|
1021
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1022
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
1023
|
+
showCopyToast();
|
|
1024
|
+
});
|
|
1025
|
+
} else {
|
|
1026
|
+
var ta = document.createElement('textarea');
|
|
1027
|
+
ta.value = text;
|
|
1028
|
+
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px';
|
|
1029
|
+
document.body.appendChild(ta);
|
|
1030
|
+
ta.select();
|
|
1031
|
+
document.execCommand('copy');
|
|
1032
|
+
document.body.removeChild(ta);
|
|
1033
|
+
showCopyToast();
|
|
1034
|
+
}
|
|
1035
|
+
} catch (e) {}
|
|
1036
|
+
return true;
|
|
1037
|
+
});
|
|
1038
|
+
|
|
989
1039
|
var modeSelect = document.getElementById('mode-select');
|
|
990
1040
|
var terminalEl = document.getElementById('terminal');
|
|
991
1041
|
var ws = null;
|
|
@@ -1020,25 +1070,15 @@
|
|
|
1020
1070
|
}
|
|
1021
1071
|
|
|
1022
1072
|
function loadInputCache() {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
console.log('[cache] already restored, skipping');
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1073
|
+
if (cacheRestored) return;
|
|
1074
|
+
cacheRestored = true;
|
|
1028
1075
|
|
|
1029
1076
|
var cached = localStorage.getItem(CACHE_KEY);
|
|
1030
|
-
if (cached
|
|
1031
|
-
console.log('[cache] restoring:', cached);
|
|
1032
|
-
//
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
// 将缓存的输入发送到终端
|
|
1037
|
-
ws.send(JSON.stringify({ type: 'input', data: cached }));
|
|
1038
|
-
// 重要:恢复后清空 currentInputBuffer,防止再次保存
|
|
1039
|
-
currentInputBuffer = '';
|
|
1040
|
-
} else {
|
|
1041
|
-
console.log('[cache] no cache to restore');
|
|
1077
|
+
if (cached) {
|
|
1078
|
+
console.log('[cache] restoring buffer:', cached);
|
|
1079
|
+
// 只恢复跟踪变量,不重新发送到 pty
|
|
1080
|
+
// 因为 outputBuffer 回放已经包含了之前的回显
|
|
1081
|
+
currentInputBuffer = cached;
|
|
1042
1082
|
}
|
|
1043
1083
|
}
|
|
1044
1084
|
|
|
@@ -1153,14 +1193,55 @@
|
|
|
1153
1193
|
pixelAccum = 0;
|
|
1154
1194
|
}
|
|
1155
1195
|
|
|
1156
|
-
//
|
|
1196
|
+
// 触摸滚动实现 - 混合模式:alternate buffer 发鼠标滚轮,normal buffer 用 scrollLines
|
|
1157
1197
|
var touchScreen = null;
|
|
1158
1198
|
var touchEventsBound = false;
|
|
1159
1199
|
|
|
1200
|
+
function isAlternateBuffer() {
|
|
1201
|
+
return term.buffer.active.type === 'alternate';
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function emitWheelEvent(lines) {
|
|
1205
|
+
var screen = terminalEl.querySelector('.xterm-screen');
|
|
1206
|
+
if (!screen) return;
|
|
1207
|
+
var lh = getLineHeight();
|
|
1208
|
+
var rect = screen.getBoundingClientRect();
|
|
1209
|
+
var cx = rect.left + rect.width / 2;
|
|
1210
|
+
var cy = rect.top + rect.height / 2;
|
|
1211
|
+
var count = Math.abs(lines);
|
|
1212
|
+
var dy = lines < 0 ? -lh : lh;
|
|
1213
|
+
for (var i = 0; i < count; i++) {
|
|
1214
|
+
screen.dispatchEvent(new WheelEvent('wheel', {
|
|
1215
|
+
deltaY: dy,
|
|
1216
|
+
deltaMode: 0,
|
|
1217
|
+
clientX: cx,
|
|
1218
|
+
clientY: cy,
|
|
1219
|
+
bubbles: true,
|
|
1220
|
+
cancelable: true,
|
|
1221
|
+
}));
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
var altScrollAccum = 0;
|
|
1226
|
+
var ALT_SCROLL_THRESHOLD = 2;
|
|
1227
|
+
|
|
1228
|
+
function doScroll(lines) {
|
|
1229
|
+
if (lines === 0) return;
|
|
1230
|
+
if (isAlternateBuffer()) {
|
|
1231
|
+
altScrollAccum += lines;
|
|
1232
|
+
if (Math.abs(altScrollAccum) >= ALT_SCROLL_THRESHOLD) {
|
|
1233
|
+
var scrollLines = Math.trunc(altScrollAccum);
|
|
1234
|
+
emitWheelEvent(scrollLines);
|
|
1235
|
+
altScrollAccum -= scrollLines;
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
term.scrollLines(lines);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1160
1242
|
function getLineHeight() {
|
|
1161
1243
|
var cellDims = getCellDims();
|
|
1162
1244
|
var height = (cellDims && cellDims.height) || 15;
|
|
1163
|
-
console.log('[scroll] lineHeight:', height);
|
|
1164
1245
|
return height;
|
|
1165
1246
|
}
|
|
1166
1247
|
|
|
@@ -1188,8 +1269,7 @@
|
|
|
1188
1269
|
var lines = Math.trunc(pixelAccum / lh);
|
|
1189
1270
|
|
|
1190
1271
|
if (lines !== 0) {
|
|
1191
|
-
|
|
1192
|
-
term.scrollLines(lines);
|
|
1272
|
+
doScroll(lines);
|
|
1193
1273
|
pixelAccum -= lines * lh;
|
|
1194
1274
|
}
|
|
1195
1275
|
}
|
|
@@ -1197,18 +1277,21 @@
|
|
|
1197
1277
|
// 长按检测
|
|
1198
1278
|
var longPressTimer = null;
|
|
1199
1279
|
var longPressTriggered = false;
|
|
1200
|
-
var LONG_PRESS_DELAY =
|
|
1280
|
+
var LONG_PRESS_DELAY = 550; // ms
|
|
1201
1281
|
|
|
1202
1282
|
function clearLongPress() {
|
|
1203
1283
|
if (longPressTimer) {
|
|
1204
1284
|
clearTimeout(longPressTimer);
|
|
1205
1285
|
longPressTimer = null;
|
|
1206
1286
|
}
|
|
1287
|
+
// 始终恢复 xterm textarea,防止 disabled 残留
|
|
1288
|
+
var xtermTa = terminalEl.querySelector('.xterm-helper-textarea');
|
|
1289
|
+
if (xtermTa) xtermTa.removeAttribute('disabled');
|
|
1207
1290
|
}
|
|
1208
1291
|
|
|
1209
1292
|
function handleTouchStart(e) {
|
|
1210
|
-
console.log('[scroll] touchstart');
|
|
1211
1293
|
stopMomentum();
|
|
1294
|
+
altScrollAccum = 0;
|
|
1212
1295
|
longPressTriggered = false;
|
|
1213
1296
|
clearLongPress();
|
|
1214
1297
|
if (e.touches.length !== 1) return;
|
|
@@ -1217,6 +1300,12 @@
|
|
|
1217
1300
|
velocitySamples = [];
|
|
1218
1301
|
|
|
1219
1302
|
// 启动长按计时器
|
|
1303
|
+
// 在长按检测期间阻止 xterm textarea 获取焦点,防止弹出键盘
|
|
1304
|
+
var xtermTa = terminalEl.querySelector('.xterm-helper-textarea');
|
|
1305
|
+
if (xtermTa) {
|
|
1306
|
+
xtermTa.setAttribute('disabled', 'true');
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1220
1309
|
longPressTimer = setTimeout(function() {
|
|
1221
1310
|
longPressTriggered = true;
|
|
1222
1311
|
longPressTimer = null;
|
|
@@ -1273,8 +1362,7 @@
|
|
|
1273
1362
|
var lh = getLineHeight();
|
|
1274
1363
|
var lines = Math.trunc(pixelAccum / lh);
|
|
1275
1364
|
if (lines !== 0) {
|
|
1276
|
-
|
|
1277
|
-
term.scrollLines(lines);
|
|
1365
|
+
doScroll(lines);
|
|
1278
1366
|
}
|
|
1279
1367
|
pixelAccum = 0;
|
|
1280
1368
|
}
|
|
@@ -1304,8 +1392,7 @@
|
|
|
1304
1392
|
var lh = getLineHeight();
|
|
1305
1393
|
var rest = Math.round(mAccum / lh);
|
|
1306
1394
|
if (rest !== 0) {
|
|
1307
|
-
|
|
1308
|
-
term.scrollLines(rest);
|
|
1395
|
+
doScroll(rest);
|
|
1309
1396
|
}
|
|
1310
1397
|
momentumRaf = null;
|
|
1311
1398
|
return;
|
|
@@ -1314,7 +1401,7 @@
|
|
|
1314
1401
|
var lh = getLineHeight();
|
|
1315
1402
|
var lines = Math.trunc(mAccum / lh);
|
|
1316
1403
|
if (lines !== 0) {
|
|
1317
|
-
|
|
1404
|
+
doScroll(lines);
|
|
1318
1405
|
mAccum -= lines * lh;
|
|
1319
1406
|
}
|
|
1320
1407
|
velocity *= friction;
|
|
@@ -1338,12 +1425,6 @@
|
|
|
1338
1425
|
// 先解绑旧的
|
|
1339
1426
|
unbindTouchScroll();
|
|
1340
1427
|
|
|
1341
|
-
// 只在 opencode 模式下绑定
|
|
1342
|
-
if (currentMode !== 'opencode') {
|
|
1343
|
-
console.log('[scroll] Not opencode mode, skip binding');
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
1428
|
var screen = terminalEl.querySelector('.xterm-screen');
|
|
1348
1429
|
if (!screen) {
|
|
1349
1430
|
console.log('[scroll] .xterm-screen not found, retrying...');
|
|
@@ -1356,7 +1437,7 @@
|
|
|
1356
1437
|
screen.addEventListener('touchmove', handleTouchMove, { passive: true });
|
|
1357
1438
|
screen.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
1358
1439
|
touchEventsBound = true;
|
|
1359
|
-
console.log('[scroll] Touch events bound to .xterm-screen
|
|
1440
|
+
console.log('[scroll] Touch events bound to .xterm-screen');
|
|
1360
1441
|
}
|
|
1361
1442
|
|
|
1362
1443
|
function rebindTouchScroll() {
|
|
@@ -1416,8 +1497,8 @@
|
|
|
1416
1497
|
} else if (d === '\x15') {
|
|
1417
1498
|
// Ctrl+U:清空整行
|
|
1418
1499
|
clearInputCache();
|
|
1419
|
-
} else if (d.
|
|
1420
|
-
//
|
|
1500
|
+
} else if (d.charCodeAt(0) >= 32 && d.charCodeAt(0) !== 127) {
|
|
1501
|
+
// 可打印字符(含中文等多字节):添加到缓冲区
|
|
1421
1502
|
currentInputBuffer += d;
|
|
1422
1503
|
saveInputCache();
|
|
1423
1504
|
}
|
|
@@ -1436,6 +1517,7 @@
|
|
|
1436
1517
|
|
|
1437
1518
|
ws.onopen = function() {
|
|
1438
1519
|
resize();
|
|
1520
|
+
rebindTouchScroll();
|
|
1439
1521
|
};
|
|
1440
1522
|
|
|
1441
1523
|
ws.onclose = function() {
|
|
@@ -1484,6 +1566,9 @@
|
|
|
1484
1566
|
// 恢复失败
|
|
1485
1567
|
term.write('\x1b[31m✗ 恢复失败: ' + msg.error + '\x1b[0m\r\n');
|
|
1486
1568
|
}
|
|
1569
|
+
else if (msg.type === 'started') {
|
|
1570
|
+
rebindTouchScroll();
|
|
1571
|
+
}
|
|
1487
1572
|
else if (msg.type === 'new-session-ok') {
|
|
1488
1573
|
isCreatingNewSession = false;
|
|
1489
1574
|
term.clear();
|
|
@@ -1500,7 +1585,6 @@
|
|
|
1500
1585
|
if (currentMode === 'opencode') {
|
|
1501
1586
|
loadInputCache();
|
|
1502
1587
|
} else {
|
|
1503
|
-
// 非 opencode 模式,标记为已恢复,避免后续缓存逻辑干扰
|
|
1504
1588
|
cacheRestored = true;
|
|
1505
1589
|
}
|
|
1506
1590
|
}, 800);
|
|
@@ -1547,10 +1631,8 @@
|
|
|
1547
1631
|
}
|
|
1548
1632
|
|
|
1549
1633
|
function scrollTerminal(lines) {
|
|
1550
|
-
if (term)
|
|
1551
|
-
|
|
1552
|
-
console.log('[scroll] scrolled by:', lines);
|
|
1553
|
-
}
|
|
1634
|
+
if (!term) return;
|
|
1635
|
+
doScroll(lines);
|
|
1554
1636
|
}
|
|
1555
1637
|
|
|
1556
1638
|
// 参考 cc-viewer 的 TerminalPanel.jsx 行 519-546: 虚拟按键触摸处理
|
|
@@ -1738,8 +1820,18 @@
|
|
|
1738
1820
|
info.appendChild(title);
|
|
1739
1821
|
info.appendChild(meta);
|
|
1740
1822
|
|
|
1823
|
+
var deleteBtn = document.createElement('button');
|
|
1824
|
+
deleteBtn.className = 'session-delete-btn';
|
|
1825
|
+
deleteBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
|
|
1826
|
+
deleteBtn.title = '删除会话';
|
|
1827
|
+
deleteBtn.addEventListener('click', function(e) {
|
|
1828
|
+
e.stopPropagation();
|
|
1829
|
+
deleteSession(session.id, item);
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1741
1832
|
item.appendChild(icon);
|
|
1742
1833
|
item.appendChild(info);
|
|
1834
|
+
item.appendChild(deleteBtn);
|
|
1743
1835
|
|
|
1744
1836
|
item.addEventListener('click', function() {
|
|
1745
1837
|
loadSession(session);
|
|
@@ -1749,6 +1841,39 @@
|
|
|
1749
1841
|
});
|
|
1750
1842
|
}
|
|
1751
1843
|
|
|
1844
|
+
function deleteSession(sessionId, itemEl) {
|
|
1845
|
+
if (!confirm('确定要删除这个会话吗?')) return;
|
|
1846
|
+
|
|
1847
|
+
itemEl.style.opacity = '0.4';
|
|
1848
|
+
itemEl.style.pointerEvents = 'none';
|
|
1849
|
+
|
|
1850
|
+
fetch('/api/session/' + sessionId, { method: 'DELETE' })
|
|
1851
|
+
.then(function(r) { return r.json(); })
|
|
1852
|
+
.then(function(data) {
|
|
1853
|
+
if (data.ok) {
|
|
1854
|
+
itemEl.style.transition = 'all 0.2s';
|
|
1855
|
+
itemEl.style.maxHeight = '0';
|
|
1856
|
+
itemEl.style.overflow = 'hidden';
|
|
1857
|
+
itemEl.style.padding = '0 12px';
|
|
1858
|
+
itemEl.style.margin = '0';
|
|
1859
|
+
itemEl.style.opacity = '0';
|
|
1860
|
+
setTimeout(function() {
|
|
1861
|
+
sessions = sessions.filter(function(s) { return s.id !== sessionId; });
|
|
1862
|
+
renderSessions();
|
|
1863
|
+
}, 200);
|
|
1864
|
+
} else {
|
|
1865
|
+
itemEl.style.opacity = '';
|
|
1866
|
+
itemEl.style.pointerEvents = '';
|
|
1867
|
+
alert('删除失败: ' + (data.error || '未知错误'));
|
|
1868
|
+
}
|
|
1869
|
+
})
|
|
1870
|
+
.catch(function(err) {
|
|
1871
|
+
itemEl.style.opacity = '';
|
|
1872
|
+
itemEl.style.pointerEvents = '';
|
|
1873
|
+
alert('删除失败: ' + err.message);
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1752
1877
|
function showSessionDetail() {
|
|
1753
1878
|
document.getElementById('session-list-view').classList.add('hidden');
|
|
1754
1879
|
document.getElementById('session-detail-view').classList.add('visible');
|
|
@@ -1996,17 +2121,6 @@
|
|
|
1996
2121
|
setTimeout(function() { toast.classList.remove('show'); }, 1200);
|
|
1997
2122
|
}
|
|
1998
2123
|
|
|
1999
|
-
// "复制" 按钮
|
|
2000
|
-
document.getElementById('btn-copy').addEventListener('touchend', function(e) {
|
|
2001
|
-
e.preventDefault();
|
|
2002
|
-
var text = getTerminalText();
|
|
2003
|
-
if (text) copyToClipboard(text);
|
|
2004
|
-
});
|
|
2005
|
-
document.getElementById('btn-copy').addEventListener('click', function(e) {
|
|
2006
|
-
e.preventDefault();
|
|
2007
|
-
var text = getTerminalText();
|
|
2008
|
-
if (text) copyToClipboard(text);
|
|
2009
|
-
});
|
|
2010
2124
|
|
|
2011
2125
|
// 方案2: 长按进入选择模式 — 原位显示可选纯文本
|
|
2012
2126
|
var selectTextLayer = document.getElementById('select-text-layer');
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -501,6 +501,30 @@ const server = createServer(async (req, res) => {
|
|
|
501
501
|
return;
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
// API: 软删除会话(设置 time_archived)
|
|
505
|
+
if (req.method === 'DELETE' && req.url?.startsWith('/api/session/')) {
|
|
506
|
+
const sessionId = req.url.split('/').pop();
|
|
507
|
+
res.writeHead(200, {
|
|
508
|
+
'Content-Type': 'application/json',
|
|
509
|
+
'Access-Control-Allow-Origin': '*',
|
|
510
|
+
});
|
|
511
|
+
try {
|
|
512
|
+
if (!existsSync(OPENCODE_DB_PATH)) {
|
|
513
|
+
res.end(JSON.stringify({ ok: false, error: '数据库不存在' }));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const db = new Database(OPENCODE_DB_PATH);
|
|
517
|
+
const now = Date.now();
|
|
518
|
+
const result = db.prepare('UPDATE session SET time_archived = ? WHERE id = ? AND time_archived IS NULL').run(now, sessionId);
|
|
519
|
+
db.close();
|
|
520
|
+
res.end(JSON.stringify({ ok: true, changes: result.changes }));
|
|
521
|
+
} catch (err) {
|
|
522
|
+
console.error('[DB] 软删除会话失败:', err.message);
|
|
523
|
+
res.end(JSON.stringify({ ok: false, error: err.message }));
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
504
528
|
// API: 获取会话消息
|
|
505
529
|
if (req.url?.startsWith('/api/session/')) {
|
|
506
530
|
const sessionId = req.url.split('/').pop();
|
|
@@ -627,6 +651,18 @@ wss.on('connection', (ws, req) => {
|
|
|
627
651
|
ws.send(JSON.stringify({ type: 'restore-error', error: e.message }));
|
|
628
652
|
}
|
|
629
653
|
}
|
|
654
|
+
} else if (msg.type === 'start') {
|
|
655
|
+
// 前端启动指令:可选带 sessionId 恢复会话
|
|
656
|
+
const mode = currentMode;
|
|
657
|
+
console.log(`[start] 启动 ${mode}, sessionId=${msg.sessionId || '(新会话)'}`);
|
|
658
|
+
outputBuffer = '';
|
|
659
|
+
try {
|
|
660
|
+
await spawnProcess(mode, msg.sessionId || null);
|
|
661
|
+
ws.send(JSON.stringify({ type: 'started', sessionId: msg.sessionId || null }));
|
|
662
|
+
} catch (e) {
|
|
663
|
+
console.error('[start] 启动失败:', e.message);
|
|
664
|
+
ws.send(JSON.stringify({ type: 'start-error', error: e.message }));
|
|
665
|
+
}
|
|
630
666
|
} else if (msg.type === 'new-session') {
|
|
631
667
|
// 开启新会话:杀掉当前进程,重新启动不带 session 参数的 opencode
|
|
632
668
|
const mode = currentMode;
|