dsh-vscode-mode 0.1.30 → 0.1.32
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/README.md +30 -0
- package/lib/client.js +591 -42
- package/lib/client.js.map +1 -1
- package/lib/index.js +1513 -123
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/capture.ts +3 -0
- package/src/client/index.ts +5 -0
- package/src/client/paths.ts +36 -0
- package/src/client/sidebar/panels/FileExplorer.ts +95 -30
- package/src/client/sidebar/panels/SearchPanel.ts +11 -2
- package/src/client/state/explorerCache.ts +3 -1
- package/src/client/state/explorerEntriesCache.ts +172 -0
- package/src/client/state/viewStateCache.ts +3 -1
- package/src/client/styles/editor.css +1 -0
- package/src/client/styles/mcp.css +48 -0
- package/src/client/ui/EditorView.ts +6 -5
- package/src/client/ui/McpSettings.ts +3 -1
- package/src/client/ui/PerfSettings.ts +242 -0
- package/src/client/ui/PerfSizeBadge.ts +54 -0
- package/src/compat.ts +3 -2
- package/src/index.ts +5 -0
- package/src/paths.ts +284 -0
- package/src/perf.ts +340 -0
- package/src/perfPatch.ts +76 -0
- package/src/routes.ts +7 -12
- package/src/rpc.ts +173 -34
- package/src/search/content.ts +14 -7
- package/src/search/ripgrep.ts +54 -1
- package/src/search/types.ts +4 -1
- package/src/shared/rpc.ts +70 -2
- package/src/store.ts +2 -2
- package/src/treeIndex.ts +397 -0
- package/src/treeRead.ts +87 -0
|
@@ -22,6 +22,7 @@ import { revealInExplorer as revealPathInExplorer } from '../fileReveal.js'
|
|
|
22
22
|
import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
|
|
23
23
|
import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
|
|
24
24
|
import { bindingOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
|
|
25
|
+
import { CACHE_KEY } from '../paths.js'
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* 中央编辑区:文件页签(脏点/关闭/打开路径)+ Ctrl+P 搜索 + Monaco 编辑器 +
|
|
@@ -64,7 +65,7 @@ export function EditorView(props) {
|
|
|
64
65
|
const [focusRequest, setFocusRequest] = React.useState(0) // 外部差异聚焦请求版本
|
|
65
66
|
const kbVersion = useKeybindingsVersion() // 快捷键配置版本(tooltip 文案随键位刷新)
|
|
66
67
|
const [hintDismissed, setHintDismissed] = React.useState(() => {
|
|
67
|
-
try { return localStorage.getItem(
|
|
68
|
+
try { return localStorage.getItem(CACHE_KEY.sideHint) === '1' } catch { return false }
|
|
68
69
|
}) // 侧边栏引导条是否已关闭
|
|
69
70
|
const editorRef = React.useRef(null)
|
|
70
71
|
const viewRootRef = React.useRef(null)
|
|
@@ -356,7 +357,7 @@ export function EditorView(props) {
|
|
|
356
357
|
bootRef.current = true
|
|
357
358
|
viewStatesRef.current = viewStatesLoad(sessionId)
|
|
358
359
|
try {
|
|
359
|
-
const raw = localStorage.getItem(
|
|
360
|
+
const raw = localStorage.getItem(CACHE_KEY.editor + String(sessionId))
|
|
360
361
|
if (raw) {
|
|
361
362
|
const saved = JSON.parse(raw)
|
|
362
363
|
if (Array.isArray(saved.tabs) && saved.tabs.length) {
|
|
@@ -369,12 +370,12 @@ export function EditorView(props) {
|
|
|
369
370
|
|
|
370
371
|
React.useEffect(() => {
|
|
371
372
|
if (!sessionId) return
|
|
372
|
-
try { localStorage.setItem(
|
|
373
|
+
try { localStorage.setItem(CACHE_KEY.editor + String(sessionId), JSON.stringify({ tabs: tabs.map((t) => t.path), active })) }
|
|
373
374
|
catch (e) { /* 忽略 */ }
|
|
374
375
|
}, [tabs, active, sessionId])
|
|
375
376
|
|
|
376
377
|
// 侧边栏状态:恢复(显隐/宽度/激活面板);侧栏形态独立键(默认收起,不共享页签形态偏好)
|
|
377
|
-
const sidebarKey =
|
|
378
|
+
const sidebarKey = CACHE_KEY.sidebar + (layout === 'side' ? 'side.' : '') + String(sessionId)
|
|
378
379
|
React.useEffect(() => {
|
|
379
380
|
if (!sessionId) return
|
|
380
381
|
try {
|
|
@@ -1152,7 +1153,7 @@ export function EditorView(props) {
|
|
|
1152
1153
|
// 侧边栏引导条(仅旧页签形态且未装 betterSidebar 时显示;可复制安装命令、可关闭)
|
|
1153
1154
|
const dismissHint = () => {
|
|
1154
1155
|
setHintDismissed(true)
|
|
1155
|
-
try { localStorage.setItem(
|
|
1156
|
+
try { localStorage.setItem(CACHE_KEY.sideHint, '1') } catch (e) { /* 忽略 */ }
|
|
1156
1157
|
}
|
|
1157
1158
|
const copyInstallCmd = () => {
|
|
1158
1159
|
if (navigator.clipboard?.writeText) navigator.clipboard.writeText(SIDEBAR_INSTALL_CMD).catch(() => {})
|
|
@@ -12,6 +12,7 @@ import '../styles/mcp.css'
|
|
|
12
12
|
import { availableOpeners, AUTO_OPEN_TOOL } from '../fileOpeners.js'
|
|
13
13
|
import { SettingsContext } from '../settingsContext.js'
|
|
14
14
|
import { KeybindingsPanel } from './KeybindingsPanel.js'
|
|
15
|
+
import { PerfSettings } from './PerfSettings.js'
|
|
15
16
|
|
|
16
17
|
const EMPTY = { serverName: '', transport: 'stdio', command: '', args: '', cwd: '', url: '', headers: '' }
|
|
17
18
|
|
|
@@ -321,11 +322,12 @@ export function McpSettings(props) {
|
|
|
321
322
|
else if (tab === 'mine') body = React.createElement('div', null, servers.length ? servers.map((s) => React.createElement(ServerCard, { key: s.id, server: s, onRefresh: refreshGlobal, onToggle: toggleGlobal, onRemove: removeGlobal })) : React.createElement('div', { className: 'vsm-mcp-empty' }, '还没有配置全局 MCP'))
|
|
322
323
|
else if (tab === 'projects') body = projectBody
|
|
323
324
|
else if (tab === 'compat') body = React.createElement(CompatSection, { getSummary: compatSummary })
|
|
325
|
+
else if (tab === 'perf') body = React.createElement(PerfSettings, null)
|
|
324
326
|
else body = marketBody
|
|
325
327
|
const form = showForm ? React.createElement(McpForm, { title: '添加全局 MCP', draft, busy, edit, save: saveGlobal, close: () => setShowForm(false) }) : projectForm ? React.createElement(McpForm, { title: '添加项目 MCP · ' + projectForm.title, draft, busy, edit, save: saveProject, close: () => setProjectForm(null) }) : null
|
|
326
328
|
return React.createElement('section', { className: 'vsm-mcp-page' },
|
|
327
329
|
React.createElement('header', { className: 'vsm-mcp-header' }, React.createElement('div', null, React.createElement('h2', null, 'VSCodeMode'), React.createElement('p', null, '管理当前 profile 与各项目的 Model Context Protocol 服务。')), React.createElement('button', { className: 'vsm-primary', onClick: () => { setDraft(EMPTY); setShowForm(true) } }, '+ 添加全局 MCP')),
|
|
328
|
-
React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'keys' ? 'active' : '', onClick: () => setTab('keys') }, '快捷键'), React.createElement('button', { className: tab === 'mine' ? 'active' : '', onClick: () => setTab('mine') }, '我的 MCP'), React.createElement('button', { className: tab === 'projects' ? 'active' : '', onClick: () => setTab('projects') }, '项目 MCP'), React.createElement('button', { className: tab === 'market' ? 'active' : '', onClick: () => setTab('market') }, 'MCP 市场'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
|
|
330
|
+
React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'keys' ? 'active' : '', onClick: () => setTab('keys') }, '快捷键'), React.createElement('button', { className: tab === 'mine' ? 'active' : '', onClick: () => setTab('mine') }, '我的 MCP'), React.createElement('button', { className: tab === 'projects' ? 'active' : '', onClick: () => setTab('projects') }, '项目 MCP'), React.createElement('button', { className: tab === 'perf' ? 'active' : '', onClick: () => setTab('perf') }, '性能优化'), React.createElement('button', { className: tab === 'market' ? 'active' : '', onClick: () => setTab('market') }, 'MCP 市场'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
|
|
329
331
|
error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
|
|
330
332
|
body,
|
|
331
333
|
form,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
|
|
4
|
+
* - 会话盘点:全工作区/每会话体积、活跃标记,按体积排序;
|
|
5
|
+
* - 移出到归档(先规划→确认→执行→可恢复)与归档清除;
|
|
6
|
+
* - DSH 内置压缩调优:写 profile patch(compaction-basic / tool-result-pruner 更低阈值,
|
|
7
|
+
* 带备份与撤销,需重启生效);
|
|
8
|
+
* - 侧车摘要用法提示(agent/脚本取关键字段,不整份读 .dsh-edit-review.json)。
|
|
9
|
+
* 作者 ddj 2026-09-02
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react'
|
|
12
|
+
import { rpc } from '../rpc.js'
|
|
13
|
+
import '../styles/mcp.css'
|
|
14
|
+
|
|
15
|
+
/** 字节数人类可读。 */
|
|
16
|
+
function formatBytes(n) {
|
|
17
|
+
if (!n) return '0B'
|
|
18
|
+
if (n >= 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + 'MB'
|
|
19
|
+
if (n >= 1024) return (n / 1024).toFixed(0) + 'KB'
|
|
20
|
+
return n + 'B'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 会话行 key:workspaceKey \u0000 sessionId。 */
|
|
24
|
+
const keyOf = (ws, id) => ws + '\u0000' + id
|
|
25
|
+
|
|
26
|
+
/** 单行会话。 */
|
|
27
|
+
function SessionRow({ row, checked, disabled, onToggle }) {
|
|
28
|
+
const age = Math.max(0, (Date.now() - row.mtime) / (24 * 3600 * 1000))
|
|
29
|
+
const when = age < 1 ? Math.round(age * 24) + 'h' : Math.round(age) + 'd'
|
|
30
|
+
return React.createElement('label', { className: 'vsm-perf-row' },
|
|
31
|
+
React.createElement('input', { type: 'checkbox', checked, disabled, onChange: onToggle }),
|
|
32
|
+
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-id', title: row.sessionId }, row.sessionId),
|
|
33
|
+
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-size ' + (row.bytes >= 1024 * 1024 ? 'big' : '') }, formatBytes(row.bytes)),
|
|
34
|
+
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-mtime', title: new Date(row.mtime).toLocaleString() }, when),
|
|
35
|
+
row.active ? React.createElement('span', { className: 'vsm-perf-cell vsm-perf-active' }, '活跃') : null,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 会话卫生 + 压缩调优设置子页。
|
|
41
|
+
* @author ddj 2026年09月02号
|
|
42
|
+
*/
|
|
43
|
+
export function PerfSettings() {
|
|
44
|
+
const [inv, setInv] = React.useState(null)
|
|
45
|
+
const [error, setError] = React.useState('')
|
|
46
|
+
const [busy, setBusy] = React.useState('')
|
|
47
|
+
const [selected, setSelected] = React.useState({})
|
|
48
|
+
const [confirm, setConfirm] = React.useState(null)
|
|
49
|
+
const [moved, setMoved] = React.useState(null)
|
|
50
|
+
const [config, setConfig] = React.useState(null)
|
|
51
|
+
|
|
52
|
+
const load = React.useCallback(() => {
|
|
53
|
+
setError('')
|
|
54
|
+
Promise.all([rpc('edrv.perf.inventory', {}), rpc('edrv.perf.configGet', {})]).then(([i, c]) => {
|
|
55
|
+
if (!i.ok) throw new Error(i.error)
|
|
56
|
+
if (!c.ok) throw new Error(c.error)
|
|
57
|
+
setInv(i)
|
|
58
|
+
setConfig(c)
|
|
59
|
+
}).catch((e) => setError(String(e)))
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
React.useEffect(load, [load])
|
|
63
|
+
|
|
64
|
+
const action = (id, method, args, patch) => {
|
|
65
|
+
setBusy(id)
|
|
66
|
+
setError('')
|
|
67
|
+
return rpc(method, args).then((res) => {
|
|
68
|
+
if (!res.ok) throw new Error(res.error)
|
|
69
|
+
if (patch) patch(res)
|
|
70
|
+
return res
|
|
71
|
+
}).catch((e) => setError(String(e))).finally(() => setBusy(''))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 圈选 ≥ 1MB 的非活跃会话。 */
|
|
75
|
+
const selectBig = () => {
|
|
76
|
+
if (!inv) return
|
|
77
|
+
const next = {}
|
|
78
|
+
for (const s of inv.sessions) {
|
|
79
|
+
if (s.bytes >= 1024 * 1024 && !s.active) next[keyOf(s.workspaceKey, s.sessionId)] = true
|
|
80
|
+
}
|
|
81
|
+
setSelected(next)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 清空选择。 */
|
|
85
|
+
const clearSel = () => setSelected({})
|
|
86
|
+
|
|
87
|
+
/** 规划移出所选(跨工作区按 sessionIds 一次规划)。 */
|
|
88
|
+
const planMove = () => {
|
|
89
|
+
const sessionIds = Object.keys(selected).map((k) => k.slice(k.indexOf('\u0000') + 1))
|
|
90
|
+
if (!sessionIds.length) return
|
|
91
|
+
action('plan', 'edrv.perf.movePlan', { sessionIds }).then((res) => {
|
|
92
|
+
if (res.items?.length) setConfirm(res)
|
|
93
|
+
else setError('所选会话没有可移出的项(活跃会话已排除)')
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 确认移出:按工作区分组逐组执行,成功后刷新并记录可恢复项。 */
|
|
98
|
+
const doMove = () => {
|
|
99
|
+
const grouped = {}
|
|
100
|
+
for (const item of confirm.items) {
|
|
101
|
+
const list = grouped[item.workspaceKey] ?? (grouped[item.workspaceKey] = [])
|
|
102
|
+
list.push(item.sessionId)
|
|
103
|
+
}
|
|
104
|
+
const jobs = Object.entries(grouped).map(([ws, sessionIds]) => rpc('edrv.perf.moveOut', { workspaceKey: ws, sessionIds }))
|
|
105
|
+
Promise.all(jobs).then((results) => {
|
|
106
|
+
const allMoved = []
|
|
107
|
+
const failures = []
|
|
108
|
+
for (const res of results) {
|
|
109
|
+
if (!res.ok) { failures.push(res.error); continue }
|
|
110
|
+
allMoved.push(...(res.moved ?? []))
|
|
111
|
+
failures.push(...(res.failures ?? []).map((f) => f.sessionId + ': ' + f.error))
|
|
112
|
+
}
|
|
113
|
+
setMoved({ moved: allMoved, failures, reclaimedBytes: allMoved.reduce((s, i) => s + i.bytes, 0) })
|
|
114
|
+
setConfirm(null)
|
|
115
|
+
setSelected({})
|
|
116
|
+
load()
|
|
117
|
+
}).catch((e) => setError(String(e)))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 恢复单个已移出会话。 */
|
|
121
|
+
const doRestore = (item) => {
|
|
122
|
+
action('restore', 'edrv.perf.restore', { workspaceKey: item.workspaceKey, sessionId: item.sessionId }).then(() => {
|
|
123
|
+
setMoved((old) => old ? { ...old, moved: old.moved.filter((m) => !(m.workspaceKey === item.workspaceKey && m.sessionId === item.sessionId)) } : old)
|
|
124
|
+
load()
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 清除归档区早于 N 天的会话(破坏性)。 */
|
|
129
|
+
const doPurge = () => {
|
|
130
|
+
if (!window.confirm('确认永久删除归档区中 30 天前的会话?此操作不可恢复(仅归档区,不影响当前 sessions)。')) return
|
|
131
|
+
action('purge', 'edrv.perf.purgeArchive', { olderThanDays: 30 }).then((res) => {
|
|
132
|
+
setError('已清除 ' + (res.removed ?? 0) + ' 个归档会话,释放 ' + formatBytes(res.reclaimedBytes ?? 0))
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** 应用压缩调优到 profile patch。 */
|
|
137
|
+
const applyConfig = () => {
|
|
138
|
+
action('apply', 'edrv.perf.configApply', {}).then((res) => {
|
|
139
|
+
setError('已写入压缩调优配置(备份 ' + res.backup + '),重启 DSH 后生效')
|
|
140
|
+
load()
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 撤销压缩调优。 */
|
|
145
|
+
const undoConfig = () => {
|
|
146
|
+
action('undo', 'edrv.perf.configUndo', {}).then(() => {
|
|
147
|
+
setError('已撤销压缩调优配置(恢复备份)')
|
|
148
|
+
load()
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const totals = inv?.totals
|
|
153
|
+
const summaryCards = totals ? React.createElement('div', { className: 'vsm-perf-cards' },
|
|
154
|
+
React.createElement('div', { className: 'vsm-perf-card' }, React.createElement('span', null, '工作区'), React.createElement('b', null, String(totals.workspaces))),
|
|
155
|
+
React.createElement('div', { className: 'vsm-perf-card' }, React.createElement('span', null, '会话数'), React.createElement('b', null, String(totals.sessions))),
|
|
156
|
+
React.createElement('div', { className: 'vsm-perf-card' }, React.createElement('span', null, '压缩态总体积'), React.createElement('b', { className: totals.totalBytes >= 50 * 1024 * 1024 ? 'big' : '' }, formatBytes(totals.totalBytes))),
|
|
157
|
+
) : null
|
|
158
|
+
|
|
159
|
+
const workspaceBlocks = inv?.workspaces ? inv.workspaces.map((ws) => {
|
|
160
|
+
const rows = inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey)
|
|
161
|
+
return React.createElement('div', { key: ws.workspaceKey, className: 'vsm-perf-ws' },
|
|
162
|
+
React.createElement('div', { className: 'vsm-perf-ws-head' },
|
|
163
|
+
React.createElement('span', { className: 'vsm-perf-ws-name', title: ws.workspaceKey }, ws.workspaceKey),
|
|
164
|
+
React.createElement('span', { className: 'vsm-perf-ws-meta' }, rows.length + ' 会话 · ' + formatBytes(ws.totalBytes)),
|
|
165
|
+
),
|
|
166
|
+
React.createElement('div', { className: 'vsm-perf-rows' },
|
|
167
|
+
rows.length ? rows.map((row) => React.createElement(SessionRow, {
|
|
168
|
+
key: keyOf(row.workspaceKey, row.sessionId),
|
|
169
|
+
row,
|
|
170
|
+
checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
|
|
171
|
+
disabled: row.active || busy === 'move',
|
|
172
|
+
onToggle: () => {
|
|
173
|
+
const k = keyOf(row.workspaceKey, row.sessionId)
|
|
174
|
+
setSelected((old) => { const next = { ...old }; if (next[k]) delete next[k]; else next[k] = true; return next })
|
|
175
|
+
},
|
|
176
|
+
})) : React.createElement('div', { className: 'vsm-mcp-empty' }, '无会话'),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
}) : null
|
|
180
|
+
|
|
181
|
+
const selCount = Object.keys(selected).length
|
|
182
|
+
const confirmBody = confirm ? React.createElement('div', { className: 'vsm-perf-modal' },
|
|
183
|
+
React.createElement('div', { className: 'vsm-perf-modal-card' },
|
|
184
|
+
React.createElement('h3', null, '确认移出 ' + confirm.items.length + ' 个会话?'),
|
|
185
|
+
React.createElement('p', { className: 'vsm-perf-modal-note' }, '将从 ~/.dsh/sessions 移出到 ~/.dsh/sessions-archive(可逆,重启后不再回放,释放 ' + formatBytes(confirm.reclaimedBytes) + ')。活跃会话已排除。'),
|
|
186
|
+
React.createElement('ul', { className: 'vsm-perf-modal-list' }, confirm.items.slice(0, 20).map((item) => React.createElement('li', { key: item.workspaceKey + item.sessionId }, item.workspaceKey + ' / ' + item.sessionId + '(' + formatBytes(item.bytes) + ')'))),
|
|
187
|
+
confirm.items.length > 20 ? React.createElement('div', { className: 'vsm-perf-modal-more' }, '…等 ' + confirm.items.length + ' 个') : null,
|
|
188
|
+
React.createElement('div', { className: 'vsm-perf-modal-actions' },
|
|
189
|
+
React.createElement('button', { className: 'vsm-primary', onClick: doMove, disabled: busy === 'move' }, busy === 'move' ? '执行中…' : '确认移出'),
|
|
190
|
+
React.createElement('button', { className: 'vsm-secondary', onClick: () => setConfirm(null) }, '取消'),
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
) : null
|
|
194
|
+
|
|
195
|
+
const movedBody = moved?.moved?.length ? React.createElement('div', { className: 'vsm-perf-moved' },
|
|
196
|
+
React.createElement('h3', null, '已移出 ' + moved.moved.length + ' 个会话(释放 ' + formatBytes(moved.reclaimedBytes) + ')'),
|
|
197
|
+
React.createElement('div', { className: 'vsm-perf-rows' }, moved.moved.map((item) => React.createElement('div', { key: item.workspaceKey + item.sessionId, className: 'vsm-perf-row' },
|
|
198
|
+
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-id' }, item.sessionId),
|
|
199
|
+
React.createElement('span', { className: 'vsm-perf-cell vsm-perf-size' }, formatBytes(item.bytes)),
|
|
200
|
+
React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: () => doRestore(item), disabled: busy === 'restore' }, '恢复'),
|
|
201
|
+
))),
|
|
202
|
+
moved.failures?.length ? React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, '失败:' + moved.failures.join(';')) : null,
|
|
203
|
+
) : null
|
|
204
|
+
|
|
205
|
+
const configBody = config ? React.createElement('div', { className: 'vsm-perf-config' },
|
|
206
|
+
React.createElement('h3', null, 'DSH 内置压缩调优'),
|
|
207
|
+
React.createElement('p', null, '调低 compaction-basic / tool-result-pruner 触发阈值,让长循环会话更早压缩,降低模型可见上下文压力(减少 token 与防 context overflow)。'),
|
|
208
|
+
React.createElement('p', { className: 'vsm-perf-note' }, '注意:压缩只追加 compaction 事件,不重写已持久化会话日志(append-only),因此不会缩小已存在的会话文件——存量瘦身请用上方「移出到归档」。packChunks 已默认开启,无需配置。'),
|
|
209
|
+
React.createElement('div', { className: 'vsm-perf-config-state' },
|
|
210
|
+
React.createElement('span', { className: 'vsm-perf-cell' }, '状态:' + (config.applied ? '已应用' : '未应用')),
|
|
211
|
+
config.applied ? React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: undoConfig, disabled: busy === 'undo' }, '撤销') : React.createElement('button', { className: 'vsm-primary vsm-small', onClick: applyConfig, disabled: busy === 'apply' }, '应用建议配置(需重启)'),
|
|
212
|
+
),
|
|
213
|
+
React.createElement('pre', { className: 'vsm-perf-block' }, config.block),
|
|
214
|
+
) : null
|
|
215
|
+
|
|
216
|
+
const guideBody = React.createElement('div', { className: 'vsm-perf-guide' },
|
|
217
|
+
React.createElement('h3', null, '侧车摘要(agent/脚本取关键字段,不整份读 sidecar)'),
|
|
218
|
+
React.createElement('p', null, '工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:'),
|
|
219
|
+
React.createElement('pre', { className: 'vsm-perf-block' },
|
|
220
|
+
"curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/json' -d '{\"method\":\"edrv.perf.sidecarSummary\",\"args\":{}}'\n# 差异记录列表(含每文件 pending 摘要):edrv.list\n# 历史归档:edrv.archiveList / edrv.archiveRead"),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return React.createElement('section', { className: 'vsm-perf-page' },
|
|
224
|
+
React.createElement('h2', null, '会话性能'),
|
|
225
|
+
React.createElement('p', null, 'DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。'),
|
|
226
|
+
error ? React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error) : null,
|
|
227
|
+
summaryCards,
|
|
228
|
+
React.createElement('div', { className: 'vsm-perf-toolbar' },
|
|
229
|
+
React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: selectBig }, '圈选 ≥1MB 非活跃'),
|
|
230
|
+
React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: clearSel, disabled: !selCount }, '清空选择'),
|
|
231
|
+
React.createElement('span', { className: 'vsm-perf-selcount' }, '已选 ' + selCount + ' 个'),
|
|
232
|
+
React.createElement('button', { className: 'vsm-primary vsm-small', onClick: planMove, disabled: !selCount || busy === 'plan' }, busy === 'plan' ? '规划中…' : '移出所选'),
|
|
233
|
+
React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: doPurge, disabled: busy === 'purge' }, '清除归档(>30天)'),
|
|
234
|
+
React.createElement('button', { className: 'vsm-secondary vsm-small', onClick: load, disabled: busy === 'load' }, '⟳ 刷新'),
|
|
235
|
+
),
|
|
236
|
+
workspaceBlocks,
|
|
237
|
+
movedBody,
|
|
238
|
+
configBody,
|
|
239
|
+
guideBody,
|
|
240
|
+
confirmBody,
|
|
241
|
+
)
|
|
242
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — PerfSizeBadge:对话 header 会话体积指示器。
|
|
4
|
+
* 5s 轮询当前会话持久化体积(host 仅 stat,近零成本),≥1MB 显示、超阈值变色,
|
|
5
|
+
* 引导"一次任务一个短会话":会话过大时建议 /compact 或新开会话。
|
|
6
|
+
* 作者 ddj 2026-09-02
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { rpc } from '../rpc.js'
|
|
10
|
+
|
|
11
|
+
/** 软阈值:≥ 此值琥珀色(默认 2MB)。 */
|
|
12
|
+
const SOFT = 2 * 1024 * 1024
|
|
13
|
+
/** 硬阈值:≥ 此值红色(默认 8MB)。 */
|
|
14
|
+
const HARD = 8 * 1024 * 1024
|
|
15
|
+
/** 显示下限:≥ 1MB 才显示(太小无提示价值)。 */
|
|
16
|
+
const SHOW_MIN = 1024 * 1024
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 会话 header 体积徽章(挂 conversation.session.header.utilities)。
|
|
20
|
+
* @author ddj 2026年09月02号
|
|
21
|
+
* @param props { sessionId, sessions }(sessions 供 cwd 解析)
|
|
22
|
+
*/
|
|
23
|
+
export function PerfSizeBadge(props) {
|
|
24
|
+
const sessionId = props?.sessionId
|
|
25
|
+
const sessions = props?.sessions
|
|
26
|
+
const [size, setSize] = React.useState(null)
|
|
27
|
+
const seq = React.useRef(0)
|
|
28
|
+
|
|
29
|
+
const load = React.useCallback(() => {
|
|
30
|
+
if (!sessionId) return
|
|
31
|
+
const cwd = sessions?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd
|
|
32
|
+
if (!cwd) return
|
|
33
|
+
const s = ++seq.current
|
|
34
|
+
rpc('edrv.perf.sessionSize', { sessionId, cwd }).then((res) => {
|
|
35
|
+
if (s !== seq.current || !res || !res.ok) return
|
|
36
|
+
setSize(res.exists ? res.bytes : 0)
|
|
37
|
+
}).catch(() => {})
|
|
38
|
+
}, [sessionId, sessions])
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
load()
|
|
42
|
+
const t = setInterval(load, 5000)
|
|
43
|
+
return () => { clearInterval(t) }
|
|
44
|
+
}, [load])
|
|
45
|
+
|
|
46
|
+
if (!size || size < SHOW_MIN) return null
|
|
47
|
+
const label = size >= 1024 * 1024 ? (size / (1024 * 1024)).toFixed(1) + 'MB' : Math.round(size / 1024) + 'KB'
|
|
48
|
+
const color = size >= HARD ? '#dc2626' : size >= SOFT ? '#d97706' : 'var(--dsw-text-3,#6b7280)'
|
|
49
|
+
const tip = '当前会话已持久化 ' + label + '(启动回放约放大 10×,占内存)。过大建议 /compact 或新开会话。'
|
|
50
|
+
return React.createElement('span', {
|
|
51
|
+
style: { display: 'inline-flex', alignItems: 'center', height: 30, padding: '0 8px', fontSize: 11, color, borderRadius: 6, cursor: 'default', whiteSpace: 'nowrap', gap: 4, lineHeight: '30px' },
|
|
52
|
+
title: tip,
|
|
53
|
+
}, '会话 ' + label)
|
|
54
|
+
}
|
package/src/compat.ts
CHANGED
|
@@ -14,6 +14,9 @@ import { loadSettingsDeps } from './fileOpenSettings.js'
|
|
|
14
14
|
import { readDevForm } from './devForm.js'
|
|
15
15
|
import type { CompatAdapter, CompatReport } from './shared/compat.js'
|
|
16
16
|
import type { Ctx } from './store.js'
|
|
17
|
+
import { ROUTE_PREFIX } from './paths.js'
|
|
18
|
+
|
|
19
|
+
export { ROUTE_PREFIX }
|
|
17
20
|
|
|
18
21
|
export const PLUGIN_NAME = 'dsh-vscode-mode'
|
|
19
22
|
export const MCP_PACKAGE = '@deepseek-ai/dsh-mcp-client'
|
|
@@ -21,8 +24,6 @@ export const MCP_PACKAGE = '@deepseek-ai/dsh-mcp-client'
|
|
|
21
24
|
export const PROJECT_ENTRY_PREFIX = 'vsm-mcp.'
|
|
22
25
|
/** 旧版项目 MCP 条目前缀(升级残留识别,不显示为全局 MCP)。 */
|
|
23
26
|
export const LEGACY_PROJECT_PREFIX = 'vsm-mcp:'
|
|
24
|
-
/** webServer 路由前缀(RPC 精确路由 + 静态资源前缀共用)。 */
|
|
25
|
-
export const ROUTE_PREFIX = '/edrv'
|
|
26
27
|
|
|
27
28
|
/** 判断新旧格式项目 MCP 条目 id(旧残留不被视为全局 MCP)。 */
|
|
28
29
|
export function isProjectEntryId(id: string): boolean {
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { installIsolation } from './mcpIsolation.js'
|
|
|
15
15
|
import { dropFileIndex } from './workspace.js'
|
|
16
16
|
import { cwdOf } from './registry.js'
|
|
17
17
|
import { setupOpenSettings } from './fileOpenSettings.js'
|
|
18
|
+
import { disposeIndex } from './treeIndex.js'
|
|
19
|
+
import { sweepTreeCache } from './paths.js'
|
|
18
20
|
import { PLUGIN_NAME, buildReport } from './compat.js'
|
|
19
21
|
import type { Registry } from './registry.js'
|
|
20
22
|
import type { Ctx } from './store.js'
|
|
@@ -47,11 +49,14 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
47
49
|
dropFileIndex(cwd)
|
|
48
50
|
searcher.dispose(cwd)
|
|
49
51
|
contentSearcher.dispose(cwd)
|
|
52
|
+
disposeIndex(cwd)
|
|
50
53
|
}
|
|
51
54
|
})
|
|
52
55
|
|
|
53
56
|
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher), (warning) => warnings.push(warning))
|
|
54
57
|
installIsolation(ctx)
|
|
58
|
+
// 启动清理缓存目录:非当前 schema / 超保留期 / 未知残留(best-effort,不阻塞装配)
|
|
59
|
+
void sweepTreeCache()
|
|
55
60
|
void logCompatSummary(ctx, warnings)
|
|
56
61
|
|
|
57
62
|
ctx.logger?.info?.('[' + name + '] 编辑差异审查已装配(/edrv/rpc 路由就绪,项目 MCP 隔离已启用)')
|