@wenbin_wb/dsh-bridge 2.6.1 → 2.7.0
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/CHANGELOG.md +127 -0
- package/README.en.md +40 -20
- package/README.md +40 -20
- package/client/client.js +568 -65
- package/client/index.js +485 -39
- package/docs/feishu-usage.md +1 -0
- package/docs/qq-usage.md +1 -0
- package/docs/telegram-usage.md +1 -0
- package/docs/wechat-usage.md +1 -0
- package/lib/auth/login-template.js +4 -0
- package/lib/bridge-rpc-constants.js +5 -0
- package/lib/bridge-rpc.js +38 -1
- package/lib/cloudflared-manager.mjs +15 -1
- package/lib/index.js +270 -2
- package/lib/platform/conversation-bridge.js +92 -46
- package/lib/qq/gateway.js +5 -2
- package/lib/telegram/gateway.js +25 -17
- package/lib/wechat/gateway.js +27 -13
- package/package.json +3 -2
package/client/index.js
CHANGED
|
@@ -109,6 +109,10 @@ const Icons = {
|
|
|
109
109
|
check: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', strokeWidth: 2.5, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
|
|
110
110
|
React.createElement('polyline', { points: '20 6 9 17 4 12' })
|
|
111
111
|
),
|
|
112
|
+
ops: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', ...props },
|
|
113
|
+
React.createElement('circle', { cx: 12, cy: 12, r: 3 }),
|
|
114
|
+
React.createElement('path', { d: 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z' })
|
|
115
|
+
),
|
|
112
116
|
};
|
|
113
117
|
|
|
114
118
|
// ---- 子组件 ----
|
|
@@ -257,6 +261,9 @@ function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
|
|
|
257
261
|
showQr && qr && React.createElement('div', { style: { marginTop: 10 } },
|
|
258
262
|
React.createElement('img', { src: qr, alt: 'QR', style: s.qr }),
|
|
259
263
|
React.createElement('div', { style: { ...s.muted, marginTop: 4 } }, '请在私密环境下使用'),
|
|
264
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 4, fontSize: 11, color: 'var(--dsw-alias-brand-primary, #4f6ef7)' } },
|
|
265
|
+
'📱 提示:手机浏览器扫码打开后,在菜单点击「添加到主屏幕」即可作为独立全屏 App 运行。'
|
|
266
|
+
),
|
|
260
267
|
),
|
|
261
268
|
onReset && React.createElement('div', { style: { marginTop: 8 } },
|
|
262
269
|
React.createElement('button', {
|
|
@@ -1445,8 +1452,6 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
1445
1452
|
),
|
|
1446
1453
|
),
|
|
1447
1454
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
1455
|
React.createElement('div', { style: s.block },
|
|
1451
1456
|
React.createElement('div', { style: { ...s.tip, fontSize: 12 } },
|
|
1452
1457
|
platformId === 'wechat'
|
|
@@ -1459,29 +1464,340 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
1459
1464
|
);
|
|
1460
1465
|
}
|
|
1461
1466
|
|
|
1462
|
-
//
|
|
1463
|
-
function
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
+
// 宿主系统运行监控看板
|
|
1468
|
+
function SystemMetricsWidget({ metrics }) {
|
|
1469
|
+
if (!metrics) return null;
|
|
1470
|
+
const memUsedPercent = metrics.memory?.usedPercent ?? 0;
|
|
1471
|
+
const memUsedGb = (metrics.memory?.usedBytes / (1024 ** 3)).toFixed(1);
|
|
1472
|
+
const memTotalGb = (metrics.memory?.totalBytes / (1024 ** 3)).toFixed(1);
|
|
1473
|
+
const heapMb = Math.round((metrics.memory?.processHeapUsed || 0) / (1024 ** 2));
|
|
1474
|
+
|
|
1475
|
+
const formatUptime = (sec = 0) => {
|
|
1476
|
+
const days = Math.floor(sec / 86400);
|
|
1477
|
+
const hrs = Math.floor((sec % 86400) / 3600);
|
|
1478
|
+
const mins = Math.floor((sec % 3600) / 60);
|
|
1479
|
+
if (days > 0) return `${days}天 ${hrs}小时 ${mins}分`;
|
|
1480
|
+
if (hrs > 0) return `${hrs}小时 ${mins}分`;
|
|
1481
|
+
return `${mins}分钟`;
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const progressColor = memUsedPercent > 85 ? '#dc2626' : memUsedPercent > 70 ? '#d97706' : '#059669';
|
|
1485
|
+
|
|
1486
|
+
return React.createElement('div', {
|
|
1487
|
+
style: {
|
|
1488
|
+
...s.card,
|
|
1489
|
+
marginBottom: 16,
|
|
1490
|
+
},
|
|
1491
|
+
},
|
|
1492
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12, flexWrap: 'wrap', gap: 6 } },
|
|
1493
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1494
|
+
'📊 宿主系统与运行看板'
|
|
1495
|
+
),
|
|
1496
|
+
React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary, #6b7280)' } },
|
|
1497
|
+
`Node ${metrics.os?.nodeVersion || ''} · ${metrics.os?.platform || ''} ${metrics.os?.arch || ''}`
|
|
1498
|
+
),
|
|
1499
|
+
),
|
|
1500
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))', gap: 12, marginBottom: 12 } },
|
|
1501
|
+
React.createElement('div', null,
|
|
1502
|
+
React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #9ca3af)', fontSize: 11, marginBottom: 2 } }, 'CPU 核心与型号'),
|
|
1503
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary, currentColor)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, title: metrics.cpu?.model },
|
|
1504
|
+
`${metrics.cpu?.cores || 0} 核心 (${(metrics.cpu?.model || '').split('@')[0].trim()})`
|
|
1505
|
+
),
|
|
1506
|
+
),
|
|
1507
|
+
React.createElement('div', null,
|
|
1508
|
+
React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #9ca3af)', fontSize: 11, marginBottom: 2 } }, 'DSH 运行时间 (Uptime)'),
|
|
1509
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-success-primary, #059669)' } },
|
|
1510
|
+
formatUptime(metrics.uptime?.processSec)
|
|
1511
|
+
),
|
|
1512
|
+
),
|
|
1513
|
+
React.createElement('div', null,
|
|
1514
|
+
React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #9ca3af)', fontSize: 11, marginBottom: 2 } }, 'Node 进程堆内存'),
|
|
1515
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary, currentColor)' } },
|
|
1516
|
+
`${heapMb} MB`
|
|
1517
|
+
),
|
|
1518
|
+
),
|
|
1519
|
+
),
|
|
1520
|
+
React.createElement('div', null,
|
|
1521
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--dsw-alias-label-secondary, #6b7280)', marginBottom: 4 } },
|
|
1522
|
+
React.createElement('span', null, `系统内存占用: ${memUsedGb} GB / ${memTotalGb} GB`),
|
|
1523
|
+
React.createElement('span', { style: { fontWeight: 600, color: progressColor } }, `${memUsedPercent}%`),
|
|
1524
|
+
),
|
|
1525
|
+
React.createElement('div', {
|
|
1526
|
+
style: {
|
|
1527
|
+
width: '100%', height: 6, background: 'var(--dsw-alias-border-l2, #e5e7eb)', borderRadius: 999, overflow: 'hidden',
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
React.createElement('div', {
|
|
1531
|
+
style: {
|
|
1532
|
+
width: `${memUsedPercent}%`, height: '100%', background: progressColor, borderRadius: 999, transition: 'width .3s ease',
|
|
1533
|
+
},
|
|
1534
|
+
}),
|
|
1535
|
+
),
|
|
1536
|
+
),
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// 网络连通性诊断小工具
|
|
1541
|
+
function NetworkDiagnosticWidget({ rpcCall }) {
|
|
1542
|
+
const [running, setRunning] = React.useState(false);
|
|
1543
|
+
const [result, setResult] = React.useState(null);
|
|
1544
|
+
|
|
1545
|
+
const runDiagnose = React.useCallback(async () => {
|
|
1546
|
+
setRunning(true);
|
|
1547
|
+
try {
|
|
1548
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.diagnoseNetwork, {});
|
|
1549
|
+
if (r?.ok) setResult(r.value);
|
|
1550
|
+
} catch (e) {
|
|
1551
|
+
setResult({ overall: 'warning', results: [{ item: 'err', name: '诊断请求异常', status: 'fail', detail: e.message }] });
|
|
1552
|
+
} finally {
|
|
1553
|
+
setRunning(false);
|
|
1554
|
+
}
|
|
1555
|
+
}, [rpcCall]);
|
|
1556
|
+
|
|
1557
|
+
return React.createElement('div', { style: { ...s.card, marginBottom: 16 } },
|
|
1558
|
+
React.createElement('div', { style: { marginBottom: 10 } },
|
|
1559
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1560
|
+
'🔍 网络连通性一键诊断'
|
|
1561
|
+
),
|
|
1562
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
1563
|
+
'一键检测本地反向代理端口、局域网 IPv4、Cloudflare Anycast 延迟以及国内 npmmirror 镜像源连通性。'
|
|
1564
|
+
),
|
|
1565
|
+
),
|
|
1566
|
+
|
|
1567
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: result ? 12 : 0 } },
|
|
1568
|
+
React.createElement('button', {
|
|
1569
|
+
type: 'button',
|
|
1570
|
+
style: { ...s.btnPri, height: 32, fontSize: 12, padding: '0 14px' },
|
|
1571
|
+
onClick: runDiagnose,
|
|
1572
|
+
disabled: running,
|
|
1573
|
+
},
|
|
1574
|
+
running ? '正在探测连通性…' : result ? '🔄 重新诊断网络' : '🔍 开始一键诊断'
|
|
1575
|
+
),
|
|
1576
|
+
result && React.createElement('span', {
|
|
1577
|
+
style: {
|
|
1578
|
+
fontSize: 12,
|
|
1579
|
+
color: result.overall === 'healthy' ? 'var(--dsw-alias-state-success-primary, #059669)' : 'var(--dsw-alias-state-warn-primary, #d97706)',
|
|
1580
|
+
fontWeight: 600,
|
|
1581
|
+
},
|
|
1582
|
+
}, result.overall === 'healthy' ? '✓ 所有网络探测项正常' : '▲ 检测到部分延迟较高或异常'),
|
|
1583
|
+
),
|
|
1584
|
+
|
|
1585
|
+
running && !result && React.createElement('div', {
|
|
1467
1586
|
style: {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
color: 'var(--dsw-alias-
|
|
1471
|
-
|
|
1472
|
-
minWidth: 0,
|
|
1473
|
-
wordBreak: 'break-all',
|
|
1474
|
-
background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
|
|
1475
|
-
padding: '4px 8px',
|
|
1476
|
-
borderRadius: 6,
|
|
1477
|
-
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
1587
|
+
marginTop: 10, padding: '10px 14px', borderRadius: 8,
|
|
1588
|
+
background: 'var(--dsw-alias-bg-layer-2, #f9fafb)',
|
|
1589
|
+
display: 'flex', alignItems: 'center', gap: 8, color: 'var(--dsw-alias-brand-primary, #4f6ef7)',
|
|
1590
|
+
fontSize: 12,
|
|
1478
1591
|
},
|
|
1479
|
-
},
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1592
|
+
},
|
|
1593
|
+
React.createElement('span', { style: { animation: 'spin 1s linear infinite', display: 'inline-flex' } }, React.createElement(Icons.refresh)),
|
|
1594
|
+
'正在执行网络端口与云端节点连通性探测…'
|
|
1595
|
+
),
|
|
1596
|
+
|
|
1597
|
+
result?.results && React.createElement('div', {
|
|
1598
|
+
style: {
|
|
1599
|
+
display: 'flex', flexDirection: 'column', gap: 8, marginTop: 10,
|
|
1600
|
+
paddingTop: 10, borderTop: '1px solid var(--dsw-alias-border-l2, #e5e7eb)',
|
|
1601
|
+
},
|
|
1602
|
+
},
|
|
1603
|
+
result.results.map((item, idx) => {
|
|
1604
|
+
const isPass = item.status === 'pass';
|
|
1605
|
+
const isWarn = item.status === 'warn';
|
|
1606
|
+
return React.createElement('div', {
|
|
1607
|
+
key: idx,
|
|
1608
|
+
style: {
|
|
1609
|
+
display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8,
|
|
1610
|
+
padding: '8px 10px', borderRadius: 6,
|
|
1611
|
+
background: 'var(--dsw-alias-bg-layer-1, rgba(255,255,255,0.7))',
|
|
1612
|
+
border: `1px solid ${isPass ? 'var(--dsw-alias-state-success-border, #a7f3d0)' : isWarn ? 'var(--dsw-alias-state-warn-border, #fde68a)' : 'var(--dsw-alias-state-error-border, #fecaca)'}`,
|
|
1613
|
+
boxSizing: 'border-box',
|
|
1614
|
+
},
|
|
1615
|
+
},
|
|
1616
|
+
React.createElement('div', { style: { flex: 1, minWidth: 0 } },
|
|
1617
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary, currentColor)', marginBottom: 2 } },
|
|
1618
|
+
isPass ? '✓ ' : isWarn ? '▲ ' : '✕ ',
|
|
1619
|
+
item.name
|
|
1620
|
+
),
|
|
1621
|
+
React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary, #6b7280)' } }, item.detail),
|
|
1622
|
+
),
|
|
1623
|
+
item.latencyMs != null && React.createElement('span', {
|
|
1624
|
+
style: {
|
|
1625
|
+
fontSize: 11, fontWeight: 600, flexShrink: 0,
|
|
1626
|
+
color: item.latencyMs < 500 ? 'var(--dsw-alias-state-success-primary, #059669)' : 'var(--dsw-alias-state-warn-primary, #d97706)',
|
|
1627
|
+
},
|
|
1628
|
+
}, `${item.latencyMs}ms`),
|
|
1629
|
+
);
|
|
1630
|
+
})
|
|
1631
|
+
)
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// 全局配置备份与恢复小卡片
|
|
1636
|
+
function BackupRestoreWidget({ rpcCall, onUpdate }) {
|
|
1637
|
+
const [exporting, setExporting] = React.useState(false);
|
|
1638
|
+
const [importing, setImporting] = React.useState(false);
|
|
1639
|
+
const [msg, setMsg] = React.useState(null);
|
|
1640
|
+
const fileInputRef = React.useRef(null);
|
|
1641
|
+
|
|
1642
|
+
const handleExport = async () => {
|
|
1643
|
+
setExporting(true);
|
|
1644
|
+
setMsg(null);
|
|
1645
|
+
try {
|
|
1646
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.exportBackup, {});
|
|
1647
|
+
if (r?.ok && r.value) {
|
|
1648
|
+
const jsonStr = JSON.stringify(r.value, null, 2);
|
|
1649
|
+
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
1650
|
+
const url = URL.createObjectURL(blob);
|
|
1651
|
+
const a = document.createElement('a');
|
|
1652
|
+
const now = new Date();
|
|
1653
|
+
const dateStr = `${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}`;
|
|
1654
|
+
a.href = url;
|
|
1655
|
+
a.download = `dsh-bridge-backup-${dateStr}.json`;
|
|
1656
|
+
document.body.appendChild(a);
|
|
1657
|
+
a.click();
|
|
1658
|
+
document.body.removeChild(a);
|
|
1659
|
+
URL.revokeObjectURL(url);
|
|
1660
|
+
setMsg({ ok: true, text: '✓ 备份文件已成功导出并下载到本地!' });
|
|
1661
|
+
} else {
|
|
1662
|
+
setMsg({ ok: false, text: r?.error?.message || '导出备份失败' });
|
|
1663
|
+
}
|
|
1664
|
+
} catch (e) {
|
|
1665
|
+
setMsg({ ok: false, text: e.message || '导出异常' });
|
|
1666
|
+
} finally {
|
|
1667
|
+
setExporting(false);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
const handleFileChange = async (e) => {
|
|
1672
|
+
const file = e.target?.files?.[0];
|
|
1673
|
+
if (!file) return;
|
|
1674
|
+
setImporting(true);
|
|
1675
|
+
setMsg(null);
|
|
1676
|
+
try {
|
|
1677
|
+
const text = await file.text();
|
|
1678
|
+
const backup = JSON.parse(text);
|
|
1679
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.importBackup, { backup });
|
|
1680
|
+
if (r?.ok) {
|
|
1681
|
+
setMsg({ ok: true, text: '✓ 配置已成功导入并刷新生效!' });
|
|
1682
|
+
onUpdate?.(r.value?.status);
|
|
1683
|
+
} else {
|
|
1684
|
+
setMsg({ ok: false, text: r?.error?.message || '导入配置失败' });
|
|
1685
|
+
}
|
|
1686
|
+
} catch (err) {
|
|
1687
|
+
setMsg({ ok: false, text: `导入解析失败: ${err.message}` });
|
|
1688
|
+
} finally {
|
|
1689
|
+
setImporting(false);
|
|
1690
|
+
if (fileInputRef.current) fileInputRef.current.value = '';
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
return React.createElement('div', { style: { ...s.card, marginBottom: 16 } },
|
|
1695
|
+
React.createElement('div', { style: { marginBottom: 10 } },
|
|
1696
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1697
|
+
'🗄️ 全局配置备份与恢复'
|
|
1698
|
+
),
|
|
1699
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
1700
|
+
'支持一键导出或导入恢复本插件所有配置(包含各 IM 平台凭证、授权白名单、公网隧道与安全认证规则)。'
|
|
1701
|
+
),
|
|
1702
|
+
),
|
|
1703
|
+
React.createElement('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1704
|
+
React.createElement('button', {
|
|
1705
|
+
type: 'button',
|
|
1706
|
+
style: { ...s.btnPri, height: 32, fontSize: 12, padding: '0 14px' },
|
|
1707
|
+
onClick: handleExport,
|
|
1708
|
+
disabled: exporting || importing,
|
|
1709
|
+
}, exporting ? '正在导出…' : '📥 导出配置备份 (.json)'),
|
|
1710
|
+
React.createElement('button', {
|
|
1711
|
+
type: 'button',
|
|
1712
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12, padding: '0 14px' },
|
|
1713
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1714
|
+
disabled: exporting || importing,
|
|
1715
|
+
}, importing ? '正在导入…' : '📤 导入配置恢复'),
|
|
1716
|
+
React.createElement('input', {
|
|
1717
|
+
type: 'file',
|
|
1718
|
+
ref: fileInputRef,
|
|
1719
|
+
accept: '.json',
|
|
1720
|
+
style: { display: 'none' },
|
|
1721
|
+
onChange: handleFileChange,
|
|
1722
|
+
}),
|
|
1723
|
+
),
|
|
1724
|
+
msg && React.createElement('div', {
|
|
1725
|
+
style: {
|
|
1726
|
+
marginTop: 10, padding: '6px 12px', borderRadius: 6, fontSize: 12,
|
|
1727
|
+
background: msg.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
1728
|
+
color: msg.ok ? 'var(--dsw-alias-state-success-primary,#059669)' : 'var(--dsw-alias-state-error-primary,#dc2626)',
|
|
1729
|
+
},
|
|
1730
|
+
}, msg.text),
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// 运维 Tab 内的手动重启 DSH 服务小卡片
|
|
1735
|
+
function RestartDshCard({ rpcCall }) {
|
|
1736
|
+
const [restarting, setRestarting] = React.useState(false);
|
|
1737
|
+
const [status, setStatus] = React.useState(null);
|
|
1738
|
+
|
|
1739
|
+
const handleRestart = async () => {
|
|
1740
|
+
setRestarting(true);
|
|
1741
|
+
setStatus({ phase: 'restarting', text: '正在向 DSH 服务发送重启指令…' });
|
|
1742
|
+
try {
|
|
1743
|
+
await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
1744
|
+
} catch {}
|
|
1745
|
+
|
|
1746
|
+
setStatus({ phase: 'reconnecting', text: 'DSH 服务正在重启中,正在自动重新连接…' });
|
|
1747
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1748
|
+
|
|
1749
|
+
let attempts = 0;
|
|
1750
|
+
const maxAttempts = 30;
|
|
1751
|
+
const pollHealth = setInterval(async () => {
|
|
1752
|
+
attempts++;
|
|
1753
|
+
try {
|
|
1754
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.checkVersion, {});
|
|
1755
|
+
if (r?.ok) {
|
|
1756
|
+
clearInterval(pollHealth);
|
|
1757
|
+
setStatus({ phase: 'success', text: '🎉 重启成功!已重新建立连接,正在刷新页面…' });
|
|
1758
|
+
setTimeout(() => { window.location.reload(); }, 1000);
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
} catch {}
|
|
1762
|
+
|
|
1763
|
+
if (attempts >= maxAttempts) {
|
|
1764
|
+
clearInterval(pollHealth);
|
|
1765
|
+
setStatus({ phase: 'timeout', text: '重连等待超时,请手动刷新页面。' });
|
|
1766
|
+
setRestarting(false);
|
|
1767
|
+
}
|
|
1768
|
+
}, 1000);
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
return React.createElement('div', { style: { ...s.card, marginBottom: 16 } },
|
|
1772
|
+
React.createElement('div', { style: { marginBottom: 10 } },
|
|
1773
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1774
|
+
'🔄 DSH 服务平滑重启'
|
|
1775
|
+
),
|
|
1776
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
1777
|
+
'优雅退出并重新拉起当前 DSH 进程与所有插件服务,前端将在几秒后自动探测重连并刷新页面。'
|
|
1778
|
+
),
|
|
1779
|
+
),
|
|
1780
|
+
!restarting && !status && React.createElement('button', {
|
|
1781
|
+
type: 'button',
|
|
1782
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12, padding: '0 14px' },
|
|
1783
|
+
onClick: handleRestart,
|
|
1784
|
+
}, '🔄 立即重启 DSH 服务'),
|
|
1785
|
+
(restarting || status) && React.createElement('div', {
|
|
1786
|
+
style: {
|
|
1787
|
+
display: 'flex', alignItems: 'center', gap: 8, fontSize: 12,
|
|
1788
|
+
color: status?.phase === 'success'
|
|
1789
|
+
? 'var(--dsw-alias-state-success-primary, #059669)'
|
|
1790
|
+
: status?.phase === 'timeout'
|
|
1791
|
+
? 'var(--dsw-alias-state-error-primary, #dc2626)'
|
|
1792
|
+
: 'var(--dsw-alias-state-info-primary, #2563eb)',
|
|
1793
|
+
fontWeight: 500,
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1796
|
+
status?.phase !== 'success' && status?.phase !== 'timeout' && React.createElement('span', {
|
|
1797
|
+
style: { animation: 'spin 1s linear infinite', display: 'inline-flex' },
|
|
1798
|
+
}, React.createElement(Icons.refresh)),
|
|
1799
|
+
status?.text || '正在调度…',
|
|
1800
|
+
),
|
|
1485
1801
|
);
|
|
1486
1802
|
}
|
|
1487
1803
|
|
|
@@ -1492,6 +1808,9 @@ function VersionBanner({ rpcCall }) {
|
|
|
1492
1808
|
const [upgrading, setUpgrading] = React.useState(false);
|
|
1493
1809
|
const [upgradeResult, setUpgradeResult] = React.useState(null);
|
|
1494
1810
|
const [showManual, setShowManual] = React.useState(false);
|
|
1811
|
+
const [restarting, setRestarting] = React.useState(false);
|
|
1812
|
+
const [restartStatus, setRestartStatus] = React.useState(null);
|
|
1813
|
+
const [dismissRestart, setDismissRestart] = React.useState(false);
|
|
1495
1814
|
|
|
1496
1815
|
const check = React.useCallback(async () => {
|
|
1497
1816
|
setLoading(true);
|
|
@@ -1512,11 +1831,12 @@ function VersionBanner({ rpcCall }) {
|
|
|
1512
1831
|
if (!info?.latest || upgrading) return;
|
|
1513
1832
|
setUpgrading(true);
|
|
1514
1833
|
setUpgradeResult(null);
|
|
1834
|
+
setDismissRestart(false);
|
|
1835
|
+
setRestartStatus(null);
|
|
1515
1836
|
try {
|
|
1516
1837
|
const r = await rpcCall(BRIDGE_ENDPOINTS.upgradePlugin, { version: info.latest });
|
|
1517
1838
|
if (r?.ok && r.value?.ok) {
|
|
1518
|
-
setUpgradeResult({ ok: true, message: `已成功升级到 v${info.latest}
|
|
1519
|
-
setTimeout(() => check(), 3000);
|
|
1839
|
+
setUpgradeResult({ ok: true, message: `已成功升级到 v${info.latest}!` });
|
|
1520
1840
|
} else {
|
|
1521
1841
|
setUpgradeResult({ ok: false, message: r?.value?.error || r?.error?.message || '升级失败' });
|
|
1522
1842
|
setShowManual(true);
|
|
@@ -1527,9 +1847,49 @@ function VersionBanner({ rpcCall }) {
|
|
|
1527
1847
|
} finally {
|
|
1528
1848
|
setUpgrading(false);
|
|
1529
1849
|
}
|
|
1530
|
-
}, [info?.latest, upgrading, rpcCall
|
|
1850
|
+
}, [info?.latest, upgrading, rpcCall]);
|
|
1851
|
+
|
|
1852
|
+
const handleRestart = React.useCallback(async () => {
|
|
1853
|
+
setRestarting(true);
|
|
1854
|
+
setRestartStatus({ phase: 'restarting', text: '正在调度 DSH 服务重启…' });
|
|
1855
|
+
try {
|
|
1856
|
+
await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
|
|
1857
|
+
} catch {
|
|
1858
|
+
// 忽略 RPC 错误(因为服务可能瞬间关闭导致网络连接断开)
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
setRestartStatus({ phase: 'reconnecting', text: 'DSH 服务正在重启中,正在自动重新连接…' });
|
|
1862
|
+
|
|
1863
|
+
// 等待 2 秒后开始健康检查轮询
|
|
1864
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1865
|
+
|
|
1866
|
+
let attempts = 0;
|
|
1867
|
+
const maxAttempts = 30; // 最多探测 30 次(约 30 秒)
|
|
1868
|
+
const pollHealth = setInterval(async () => {
|
|
1869
|
+
attempts++;
|
|
1870
|
+
try {
|
|
1871
|
+
const r = await rpcCall(BRIDGE_ENDPOINTS.checkVersion, {});
|
|
1872
|
+
if (r?.ok) {
|
|
1873
|
+
clearInterval(pollHealth);
|
|
1874
|
+
setRestartStatus({ phase: 'success', text: '🎉 重启成功!已自动加载最新版本。正在刷新页面…' });
|
|
1875
|
+
setTimeout(() => {
|
|
1876
|
+
window.location.reload();
|
|
1877
|
+
}, 1000);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
} catch {
|
|
1881
|
+
// 仍在启动中,继续等待
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (attempts >= maxAttempts) {
|
|
1885
|
+
clearInterval(pollHealth);
|
|
1886
|
+
setRestartStatus({ phase: 'timeout', text: '重连等待超时,请手动刷新页面。' });
|
|
1887
|
+
setRestarting(false);
|
|
1888
|
+
}
|
|
1889
|
+
}, 1000);
|
|
1890
|
+
}, [rpcCall]);
|
|
1531
1891
|
|
|
1532
|
-
const links = React.createElement('div', { style: { display: 'flex', gap:
|
|
1892
|
+
const links = React.createElement('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1533
1893
|
React.createElement('a', {
|
|
1534
1894
|
href: GITHUB_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
|
|
1535
1895
|
}, React.createElement(Icons.github), 'GitHub'),
|
|
@@ -1598,7 +1958,7 @@ function VersionBanner({ rpcCall }) {
|
|
|
1598
1958
|
gap: 4,
|
|
1599
1959
|
},
|
|
1600
1960
|
onClick: check,
|
|
1601
|
-
disabled: loading || upgrading,
|
|
1961
|
+
disabled: loading || upgrading || restarting,
|
|
1602
1962
|
title: '重新检查 npm 线上版本',
|
|
1603
1963
|
},
|
|
1604
1964
|
React.createElement(Icons.refresh),
|
|
@@ -1639,10 +1999,10 @@ function VersionBanner({ rpcCall }) {
|
|
|
1639
1999
|
background: upgradeResult?.ok
|
|
1640
2000
|
? 'var(--dsw-alias-state-success-primary,#059669)'
|
|
1641
2001
|
: 'var(--dsw-alias-brand-primary,#4f6ef7)',
|
|
1642
|
-
opacity: upgrading ? 0.6 : 1,
|
|
2002
|
+
opacity: (upgrading || restarting) ? 0.6 : 1,
|
|
1643
2003
|
},
|
|
1644
2004
|
onClick: handleUpgrade,
|
|
1645
|
-
disabled: upgrading || upgradeResult?.ok,
|
|
2005
|
+
disabled: upgrading || restarting || upgradeResult?.ok,
|
|
1646
2006
|
},
|
|
1647
2007
|
upgrading
|
|
1648
2008
|
? React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
|
@@ -1650,7 +2010,7 @@ function VersionBanner({ rpcCall }) {
|
|
|
1650
2010
|
'正在自动升级…',
|
|
1651
2011
|
)
|
|
1652
2012
|
: upgradeResult?.ok
|
|
1653
|
-
? '✓
|
|
2013
|
+
? '✓ 升级完成'
|
|
1654
2014
|
: `一键升级到 v${info.latest}`
|
|
1655
2015
|
),
|
|
1656
2016
|
),
|
|
@@ -1659,9 +2019,9 @@ function VersionBanner({ rpcCall }) {
|
|
|
1659
2019
|
info?.releaseNotes && React.createElement('div', {
|
|
1660
2020
|
style: {
|
|
1661
2021
|
fontSize: 12,
|
|
1662
|
-
color: 'var(--dsw-alias-label-
|
|
1663
|
-
background: 'rgba(255, 255, 255, 0.
|
|
1664
|
-
border: '1px solid rgba(191, 219, 254, 0.
|
|
2022
|
+
color: 'var(--dsw-alias-label-primary, #374151)',
|
|
2023
|
+
background: 'var(--dsw-alias-bg-layer-2, rgba(255, 255, 255, 0.85))',
|
|
2024
|
+
border: '1px solid var(--dsw-alias-state-info-border, rgba(191, 219, 254, 0.8))',
|
|
1665
2025
|
borderRadius: 8,
|
|
1666
2026
|
padding: '8px 12px',
|
|
1667
2027
|
marginBottom: 10,
|
|
@@ -1669,15 +2029,76 @@ function VersionBanner({ rpcCall }) {
|
|
|
1669
2029
|
whiteSpace: 'pre-line',
|
|
1670
2030
|
},
|
|
1671
2031
|
},
|
|
1672
|
-
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-info-primary
|
|
2032
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-info-primary, #2563eb)', marginBottom: 2 } }, '✨ 更新亮点:'),
|
|
1673
2033
|
info.releaseNotes
|
|
1674
2034
|
),
|
|
1675
2035
|
|
|
1676
|
-
|
|
2036
|
+
// 升级成功后:引导重启 DSH 操作卡片
|
|
2037
|
+
upgradeResult?.ok && !dismissRestart && React.createElement('div', {
|
|
2038
|
+
style: {
|
|
2039
|
+
background: 'var(--dsw-alias-bg-layer-2, rgba(255, 255, 255, 0.95))',
|
|
2040
|
+
border: '1px solid var(--dsw-alias-state-success-border, #a7f3d0)',
|
|
2041
|
+
borderRadius: 8,
|
|
2042
|
+
padding: '12px 14px',
|
|
2043
|
+
marginBottom: 10,
|
|
2044
|
+
},
|
|
2045
|
+
},
|
|
2046
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 } },
|
|
2047
|
+
React.createElement('span', { style: { fontSize: 16 } }, '✨'),
|
|
2048
|
+
React.createElement('span', {
|
|
2049
|
+
style: { fontSize: 13, fontWeight: 600, color: 'var(--dsw-alias-state-success-primary, #059669)' },
|
|
2050
|
+
}, `已成功升级到 v${info.latest}!需要重启 DSH 服务使新版本生效`),
|
|
2051
|
+
),
|
|
2052
|
+
!restarting && !restartStatus && React.createElement('div', {
|
|
2053
|
+
style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' },
|
|
2054
|
+
},
|
|
2055
|
+
React.createElement('button', {
|
|
2056
|
+
style: {
|
|
2057
|
+
...s.btnPri,
|
|
2058
|
+
height: 30,
|
|
2059
|
+
fontSize: 12,
|
|
2060
|
+
padding: '0 14px',
|
|
2061
|
+
background: 'var(--dsw-alias-state-success-primary, #059669)',
|
|
2062
|
+
},
|
|
2063
|
+
onClick: handleRestart,
|
|
2064
|
+
}, '🔄 立即重启 DSH 服务'),
|
|
2065
|
+
React.createElement('button', {
|
|
2066
|
+
style: {
|
|
2067
|
+
...s.btnGhost,
|
|
2068
|
+
height: 30,
|
|
2069
|
+
fontSize: 12,
|
|
2070
|
+
padding: '0 12px',
|
|
2071
|
+
},
|
|
2072
|
+
onClick: () => setDismissRestart(true),
|
|
2073
|
+
}, '稍后手动重启'),
|
|
2074
|
+
),
|
|
2075
|
+
(restarting || restartStatus) && React.createElement('div', {
|
|
2076
|
+
style: {
|
|
2077
|
+
display: 'flex',
|
|
2078
|
+
alignItems: 'center',
|
|
2079
|
+
gap: 8,
|
|
2080
|
+
fontSize: 12,
|
|
2081
|
+
color: restartStatus?.phase === 'success'
|
|
2082
|
+
? 'var(--dsw-alias-state-success-primary, #059669)'
|
|
2083
|
+
: restartStatus?.phase === 'timeout'
|
|
2084
|
+
? 'var(--dsw-alias-state-error-primary, #dc2626)'
|
|
2085
|
+
: 'var(--dsw-alias-state-info-primary, #2563eb)',
|
|
2086
|
+
fontWeight: 500,
|
|
2087
|
+
},
|
|
2088
|
+
},
|
|
2089
|
+
restartStatus?.phase !== 'success' && restartStatus?.phase !== 'timeout' && React.createElement('span', {
|
|
2090
|
+
style: { animation: 'spin 1s linear infinite', display: 'inline-flex' },
|
|
2091
|
+
}, React.createElement(Icons.refresh)),
|
|
2092
|
+
restartStatus?.text || '正在处理…',
|
|
2093
|
+
),
|
|
2094
|
+
),
|
|
2095
|
+
|
|
2096
|
+
// 失败提示
|
|
2097
|
+
upgradeResult && !upgradeResult.ok && React.createElement('div', {
|
|
1677
2098
|
style: {
|
|
1678
|
-
background:
|
|
1679
|
-
border:
|
|
1680
|
-
color:
|
|
2099
|
+
background: 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
2100
|
+
border: '1px solid var(--dsw-alias-state-error-border,#fecaca)',
|
|
2101
|
+
color: 'var(--dsw-alias-state-error-primary,#991b1b)',
|
|
1681
2102
|
padding: '8px 12px',
|
|
1682
2103
|
borderRadius: 6,
|
|
1683
2104
|
fontSize: 12,
|
|
@@ -1713,15 +2134,19 @@ const TABS = [
|
|
|
1713
2134
|
{ id: 'tunnel', label: '公网隧道', icon: Icons.tunnel },
|
|
1714
2135
|
{ id: 'im', label: 'IM 机器人', icon: Icons.bot },
|
|
1715
2136
|
{ id: 'security', label: '安全认证', icon: Icons.security },
|
|
2137
|
+
{ id: 'ops', label: '运维监控', icon: Icons.ops },
|
|
1716
2138
|
];
|
|
1717
2139
|
|
|
1718
2140
|
function TabBar({ active, onChange, dots }) {
|
|
1719
2141
|
return React.createElement('div', {
|
|
2142
|
+
className: 'dsh-tabbar-container',
|
|
1720
2143
|
style: {
|
|
1721
2144
|
display: 'flex', gap: 4, marginBottom: 20,
|
|
1722
2145
|
borderBottom: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
1723
2146
|
overflowX: 'auto', WebkitOverflowScrolling: 'touch',
|
|
1724
2147
|
maxWidth: '100%', flexWrap: 'nowrap',
|
|
2148
|
+
scrollbarWidth: 'none',
|
|
2149
|
+
msOverflowStyle: 'none',
|
|
1725
2150
|
},
|
|
1726
2151
|
},
|
|
1727
2152
|
TABS.map(({ id, label, icon: TabIcon }) => {
|
|
@@ -2052,6 +2477,16 @@ function BridgePanel({ rpcCall }) {
|
|
|
2052
2477
|
rpcCall: authRpcCall,
|
|
2053
2478
|
onUpdate: () => load(true),
|
|
2054
2479
|
});
|
|
2480
|
+
} else if (activeTab === 'ops') {
|
|
2481
|
+
tabContent = React.createElement(React.Fragment, null,
|
|
2482
|
+
React.createElement(SystemMetricsWidget, { metrics: status?.system }),
|
|
2483
|
+
React.createElement(NetworkDiagnosticWidget, { rpcCall: authRpcCall }),
|
|
2484
|
+
React.createElement(BackupRestoreWidget, {
|
|
2485
|
+
rpcCall: authRpcCall,
|
|
2486
|
+
onUpdate: () => load(true),
|
|
2487
|
+
}),
|
|
2488
|
+
React.createElement(RestartDshCard, { rpcCall: authRpcCall }),
|
|
2489
|
+
);
|
|
2055
2490
|
} else if (activeTab === 'im') {
|
|
2056
2491
|
// 从 listPlatforms 动态生成平台列表
|
|
2057
2492
|
const IM_PLATFORMS = [
|
|
@@ -2348,6 +2783,17 @@ function injectMobileStyles() {
|
|
|
2348
2783
|
const style = document.createElement('style');
|
|
2349
2784
|
style.id = 'dsh-bridge-mobile-styles';
|
|
2350
2785
|
style.textContent = `
|
|
2786
|
+
/* DSH Bridge 隐藏 Tab 栏原生滚动条并保持平滑滑动 */
|
|
2787
|
+
.dsh-tabbar-container {
|
|
2788
|
+
scrollbar-width: none !important;
|
|
2789
|
+
-ms-overflow-style: none !important;
|
|
2790
|
+
}
|
|
2791
|
+
.dsh-tabbar-container::-webkit-scrollbar {
|
|
2792
|
+
display: none !important;
|
|
2793
|
+
width: 0 !important;
|
|
2794
|
+
height: 0 !important;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2351
2797
|
/* DSH Bridge 移动端自适应与触控交互增强样式 */
|
|
2352
2798
|
:root {
|
|
2353
2799
|
--dsh-mobile-header-h: 52px;
|
package/docs/feishu-usage.md
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
| `/use <编号>` | 切换到指定编号会话 | `/use 1` 或 `/resume 1` |
|
|
85
85
|
| `/new <提示词>` | 在当前工作区创建新会话并开始 | `/new 帮我写个脚本` |
|
|
86
86
|
| `/new <词> @N` | 在指定工作区新建会话 | `/new 帮我写个脚本 @1` |
|
|
87
|
+
| `/rename <新标题>` | 重命名当前活动会话 | `/rename 优化登录交互` |
|
|
87
88
|
| `/workspaces` | 查看所有已注册的工作区列表 | `/workspaces` |
|
|
88
89
|
| `/status` | 查看 Agent 运行状态看板 | `/status` |
|
|
89
90
|
| `/stop` | 中断停止当前正在执行的任务 | `/stop` |
|
package/docs/qq-usage.md
CHANGED
|
@@ -184,6 +184,7 @@ AI 生成过程中,QQ 会显示机器人的"正在输入"状态:
|
|
|
184
184
|
- `/new <提示词>` - 新建会话并开始
|
|
185
185
|
- `/sessions`(或 `/list`)- 列出所有会话(按工作区分组)
|
|
186
186
|
- `/use N`(或 `/resume N`)- 切换到/恢复会话 N
|
|
187
|
+
- `/rename <新标题>` - 重命名当前活动会话
|
|
187
188
|
- `/end` - 结束当前会话(清除活动会话并触发快捷按钮)
|
|
188
189
|
- `/stop` - 停止当前任务
|
|
189
190
|
- `/status` - 查看状态与会话摘要
|
package/docs/telegram-usage.md
CHANGED
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
| *(普通文本)* | 发送给当前活动 Agent 执行任务 | 实时打字机流式输出 |
|
|
70
70
|
| `/new <提示词>` | 在当前工作区新建会话并立即执行 | 启动全新轮次 |
|
|
71
71
|
| `/new <提示词> @N` | 在指定工作区序号新建会话 | 多工作区调度 |
|
|
72
|
+
| `/rename <新标题>` | 重命名当前活动会话标题 | 修改会话名称 |
|
|
72
73
|
| `/sessions`(或 `/list`) | 查看所有会话列表 | 附带一键切换按键 |
|
|
73
74
|
| `/use N`(或 `/resume N`) | 切换活动会话至序号 N | 快速切换上下文 |
|
|
74
75
|
| `/workspaces` | 列出本地所有可用项目工作区 | 查看工作区路径 |
|