dsh-vscode-mode 0.14.3 → 0.14.5
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 +20 -9
- package/lib/client.js +1445 -400
- package/lib/client.js.map +1 -1
- package/lib/index.js +242 -53
- package/lib/index.js.map +1 -1
- package/package.json +7 -7
- package/skills/dsh-vscodemode-mcp/SKILL.md +9 -3
- package/src/client/diffDockStore.ts +8 -3
- package/src/client/index.ts +2 -0
- package/src/client/mcpForm.ts +79 -0
- package/src/client/mcpState.ts +179 -0
- package/src/client/monaco/diffRender.ts +234 -174
- package/src/client/monaco/theme.ts +26 -15
- package/src/client/officialSidebar.ts +6 -1
- package/src/client/openReceipt.ts +13 -3
- package/src/client/planBridge.ts +178 -0
- package/src/client/planState.ts +129 -0
- package/src/client/receiptState.ts +1 -0
- package/src/client/state/regions.ts +83 -3
- package/src/client/styles/editor.css +8 -7
- package/src/client/styles/mcp.css +6 -3
- package/src/client/ui/ClaimRouter.ts +1 -1
- package/src/client/ui/EditorView.ts +107 -36
- package/src/client/ui/McpSettings.ts +174 -137
- package/src/client/ui/OfficialSideTab.ts +2 -2
- package/src/dshVersion.ts +4 -3
- package/src/mcp.ts +131 -23
- package/src/mcpIsolation.ts +2 -1
- package/src/mcpProject.ts +74 -14
- package/src/mcpRuntime.ts +18 -3
- package/src/rpc.ts +2 -1
- package/src/shared/mcpStatus.ts +30 -0
- package/src/shared/rpc.ts +2 -0
|
@@ -18,6 +18,9 @@ import { clampZoom, clearZoomMem, dataUrlOf, isImagePath, isSvgPath, recallZoom,
|
|
|
18
18
|
import { isMarkdownPath } from '../markdownPreview.js'
|
|
19
19
|
import { setActiveEditorPath } from '../activePathStore.js'
|
|
20
20
|
import { MarkdownPanel } from '../md/mdPanel.js'
|
|
21
|
+
import { isPlanPath, planDocument } from '../planState.js'
|
|
22
|
+
import { MarkdownText, MarkdownDelegateProvider } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
23
|
+
import { isComponentType } from '../md/componentType.js'
|
|
21
24
|
import { base64ToBytes, bytesToBase64, isPdfPath } from '../pdfPreview.js'
|
|
22
25
|
import { inNativePath, tryNativeOpen } from '../nativeOpenStore.js'
|
|
23
26
|
import { clearPendingNav, putPendingNav, readPendingNav } from '../pendingNav.js'
|
|
@@ -126,6 +129,8 @@ function syncTypeSuggest(ed, monaco) {
|
|
|
126
129
|
|
|
127
130
|
/** 日志弹窗每次加载条数(「加载更多」按此步长递增;上限由 host 的 SVN_LOG_SHOW_ALL_CAP 约束)。 */
|
|
128
131
|
const SVN_LOG_PAGE = 100
|
|
132
|
+
/** Stable Markdown chrome for virtual plans; content stays in session-local memory. */
|
|
133
|
+
const PLAN_MD_LABELS = { code: { copyLabel: '复制', copiedLabel: '已复制' }, footnotes: '脚注' }
|
|
129
134
|
|
|
130
135
|
/**
|
|
131
136
|
* 日志弹窗状态 → 取数选项(P1-4/P1-5/P1-6:soc/mrg/range 与状态层键保持同源)。
|
|
@@ -238,7 +243,15 @@ export function EditorView(props) {
|
|
|
238
243
|
const [monacoErr, setMonacoErr] = React.useState(null)
|
|
239
244
|
const [records, setRecords] = React.useState({})
|
|
240
245
|
const [tabs, setTabs] = React.useState([])
|
|
241
|
-
const [active,
|
|
246
|
+
const [active, setDiskActive] = React.useState(null)
|
|
247
|
+
const [planTabs, setPlanTabs] = React.useState([])
|
|
248
|
+
const [planActive, setPlanActive] = React.useState(null)
|
|
249
|
+
const planActiveRef = React.useRef(null)
|
|
250
|
+
planActiveRef.current = planActive
|
|
251
|
+
const planHostRef = React.useRef(null)
|
|
252
|
+
React.useEffect(() => { setPlanTabs([]); setPlanActive(null) }, [sessionId, cwd])
|
|
253
|
+
/** @private @author ddj 2026年10月08号 @param path Disk tab selection. Leave read-only plan mode before any disk navigation. */
|
|
254
|
+
function setActive(path) { setPlanActive(null); setDiskActive(path) }
|
|
242
255
|
const [dirtyMap, setDirtyMap] = React.useState({})
|
|
243
256
|
const [content, setContent] = React.useState(null)
|
|
244
257
|
const [contentPath, setContentPath] = React.useState(null)
|
|
@@ -512,9 +525,13 @@ export function EditorView(props) {
|
|
|
512
525
|
lineRegionMapRef.current = lineRegionMap
|
|
513
526
|
const sum = React.useMemo(() => summarize(Object.values(records)), [records])
|
|
514
527
|
|
|
515
|
-
|
|
528
|
+
/** @private @author ddj 2026年10月08号 @param path Disk path. @param select Select tab. @param preview Optional explicit Markdown mode. */
|
|
529
|
+
function addTab(path, select, preview) {
|
|
516
530
|
setTabs((prev) => insertTab(prev, path))
|
|
517
|
-
if (select)
|
|
531
|
+
if (select) {
|
|
532
|
+
if (typeof preview === 'boolean' || isPlanPath(path, cwd, sessionId)) setMdPreview(path, typeof preview === 'boolean' ? preview : true)
|
|
533
|
+
setActive(path)
|
|
534
|
+
}
|
|
518
535
|
}
|
|
519
536
|
|
|
520
537
|
/**
|
|
@@ -536,11 +553,11 @@ export function EditorView(props) {
|
|
|
536
553
|
* @param select 是否设为活动页签
|
|
537
554
|
* @returns 归一后的路径(空值返回 null)
|
|
538
555
|
*/
|
|
539
|
-
const addTabNorm = (path, select) => {
|
|
556
|
+
const addTabNorm = (path, select, preview) => {
|
|
540
557
|
if (!path) return null
|
|
541
558
|
const normalized = tabPath(path)
|
|
542
559
|
if (!normalized) return null
|
|
543
|
-
addTab(normalized, select)
|
|
560
|
+
addTab(normalized, select, preview)
|
|
544
561
|
return normalized
|
|
545
562
|
}
|
|
546
563
|
|
|
@@ -1238,28 +1255,32 @@ export function EditorView(props) {
|
|
|
1238
1255
|
return () => window.removeEventListener('edrv:snippets-changed', onChanged)
|
|
1239
1256
|
}, [])
|
|
1240
1257
|
|
|
1241
|
-
React.
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
pendingFocusRef.current = { path: normalized, region: null }
|
|
1251
|
-
setFocusRequest((value) => value + 1)
|
|
1252
|
-
addTab(normalized, true)
|
|
1253
|
-
return
|
|
1254
|
-
}
|
|
1255
|
-
if (e?.detail?.line != null) {
|
|
1256
|
-
// LSP/搜索跳转:打开并定位到行列(endLine/endColumn 为目标区间,供落地高亮)
|
|
1257
|
-
openFileAt(normalized, e?.detail?.line, e?.detail?.column, e?.detail?.endLine, e?.detail?.endColumn)
|
|
1258
|
-
return
|
|
1259
|
-
}
|
|
1258
|
+
const fileOpenRef = React.useRef(null)
|
|
1259
|
+
/** @private @author ddj 2026年10月08号 @param e Routed file request. Always uses the current workspace and save/preview callbacks. */
|
|
1260
|
+
fileOpenRef.current = (e) => {
|
|
1261
|
+
const p = e?.detail?.path
|
|
1262
|
+
if (!p) return
|
|
1263
|
+
// G9:入口统一归一为工作区相对路径(差异栏/对话链接/LSP 跳转等都经此事件)
|
|
1264
|
+
const normalized = tabPath(p)
|
|
1265
|
+
if (!normalized) return
|
|
1266
|
+
if (e?.detail?.focusDiff === true) {
|
|
1260
1267
|
recordNav()
|
|
1261
|
-
|
|
1268
|
+
pendingFocusRef.current = { path: normalized, region: null }
|
|
1269
|
+
setFocusRequest((value) => value + 1)
|
|
1270
|
+
addTab(normalized, true, false)
|
|
1271
|
+
return
|
|
1262
1272
|
}
|
|
1273
|
+
if (e?.detail?.line != null) {
|
|
1274
|
+
// LSP/搜索跳转:打开并定位到行列(endLine/endColumn 为目标区间,供落地高亮)
|
|
1275
|
+
openFileAt(normalized, e?.detail?.line, e?.detail?.column, e?.detail?.endLine, e?.detail?.endColumn)
|
|
1276
|
+
return
|
|
1277
|
+
}
|
|
1278
|
+
recordNav()
|
|
1279
|
+
addTab(normalized, true, e?.detail?.preview)
|
|
1280
|
+
}
|
|
1281
|
+
React.useEffect(() => {
|
|
1282
|
+
/** @private @author ddj 2026年10月08号 @param event File navigation event. Dispatch to the latest render closure. */
|
|
1283
|
+
const onOpen = (event) => fileOpenRef.current?.(event)
|
|
1263
1284
|
const onShowLauncher = (event) => {
|
|
1264
1285
|
const tab = event?.detail?.tab
|
|
1265
1286
|
if (tab === 'pending' || tab === 'archive') setLauncherTab(tab)
|
|
@@ -1807,7 +1828,11 @@ export function EditorView(props) {
|
|
|
1807
1828
|
React.useEffect(() => {
|
|
1808
1829
|
if (!monaco) return
|
|
1809
1830
|
registerThemes(monaco)
|
|
1810
|
-
|
|
1831
|
+
/** @private @author ddj 2026年10月09号 同步 Monaco 主题与行内删除代码着色。 */
|
|
1832
|
+
const apply = () => {
|
|
1833
|
+
const themeId = applyOfficial(monaco)
|
|
1834
|
+
diffRendererRef.current.refresh(themeId)
|
|
1835
|
+
}
|
|
1811
1836
|
apply()
|
|
1812
1837
|
window.addEventListener('edrv:theme-change', apply)
|
|
1813
1838
|
const query = typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-color-scheme: dark)') : null
|
|
@@ -2619,13 +2644,19 @@ export function EditorView(props) {
|
|
|
2619
2644
|
* @author ddj 2026年09月18号
|
|
2620
2645
|
* @param path Markdown 文件路径
|
|
2621
2646
|
*/
|
|
2647
|
+
/** @private @author ddj 2026年10月08号 @param path Markdown disk path. @param preview Desired mode; explicit line opens always select source. */
|
|
2648
|
+
function setMdPreview(path, preview) {
|
|
2649
|
+
if (!isMarkdownPath(path) || mdPreviewRef.current.has(path) === preview) return
|
|
2650
|
+
toggleMdPreview(path)
|
|
2651
|
+
}
|
|
2652
|
+
/** @private @author ddj 2026年10月08号 @param path Markdown file. Toggle preview after flushing the active file's save and view state. */
|
|
2622
2653
|
const toggleMdPreview = (path) => {
|
|
2623
2654
|
if (!path) return
|
|
2624
2655
|
const next = new Set(mdPreviewRef.current)
|
|
2625
2656
|
const entering = !next.has(path)
|
|
2626
2657
|
if (entering) {
|
|
2627
2658
|
flushSave()
|
|
2628
|
-
saveViewState(
|
|
2659
|
+
saveViewState(active)
|
|
2629
2660
|
next.add(path)
|
|
2630
2661
|
} else {
|
|
2631
2662
|
next.delete(path)
|
|
@@ -3220,6 +3251,7 @@ export function EditorView(props) {
|
|
|
3220
3251
|
|
|
3221
3252
|
/** 关闭当前活动页签(Ctrl+F4 与菜单动作共用;无页签时提示)。 */
|
|
3222
3253
|
const closeActiveTab = () => {
|
|
3254
|
+
if (planActiveRef.current) { closePlan(planActiveRef.current); return }
|
|
3223
3255
|
if (!activeRef.current) { setStatus('无打开的文件'); return }
|
|
3224
3256
|
closeTab(activeRef.current)
|
|
3225
3257
|
}
|
|
@@ -3346,7 +3378,7 @@ export function EditorView(props) {
|
|
|
3346
3378
|
dapTrace('open-file-at', { input: path, normalized: tracePath, line, cwd })
|
|
3347
3379
|
recordNav()
|
|
3348
3380
|
// G9:同 openFile,先归一再进页签,保证待跳转路径与 active 同形态
|
|
3349
|
-
const normalized = addTabNorm(path, true)
|
|
3381
|
+
const normalized = addTabNorm(path, true, false)
|
|
3350
3382
|
if (!normalized) return
|
|
3351
3383
|
stageFileAt(normalized, line, column, endLine, endColumn)
|
|
3352
3384
|
}
|
|
@@ -3366,12 +3398,28 @@ export function EditorView(props) {
|
|
|
3366
3398
|
setFocusRequest((value) => value + 1)
|
|
3367
3399
|
}
|
|
3368
3400
|
|
|
3369
|
-
/**
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3401
|
+
/** @private @author ddj 2026年10月08号 @param plan Complete virtual document. Keeps virtual addresses out of all disk/save/LSP/SVN flows. */
|
|
3402
|
+
function openPlanDoc(plan) {
|
|
3403
|
+
const document = planDocument(plan?.address, plan)
|
|
3404
|
+
if (!document) return
|
|
3405
|
+
flushSave()
|
|
3406
|
+
saveViewState(active)
|
|
3407
|
+
setSvnDiff(null)
|
|
3408
|
+
setDiskActive(null)
|
|
3409
|
+
setPlanTabs((prev) => [...prev.filter((item) => item.address !== document.address), document].slice(-32))
|
|
3410
|
+
setPlanActive(document.address)
|
|
3411
|
+
setError(null)
|
|
3412
|
+
setStatus('计划预览 · 只读')
|
|
3413
|
+
}
|
|
3414
|
+
/** @private @author ddj 2026年10月08号 @param address Virtual tab. @param event Optional close click. Removes only in-memory plan content. */
|
|
3415
|
+
function closePlan(address, event) {
|
|
3416
|
+
event?.stopPropagation()
|
|
3417
|
+
setPlanTabs((prev) => prev.filter((item) => item.address !== address))
|
|
3418
|
+
if (planActiveRef.current === address) setActive(tabsRef.current[0]?.path ?? null)
|
|
3419
|
+
}
|
|
3420
|
+
/** @private @author ddj 2026年10月08号 @param request Claimed disk or virtual open. Flushes saves without replacing dirty models. */
|
|
3374
3421
|
function openReceiptFile(request) {
|
|
3422
|
+
if (request.plan) { openPlanDoc(request.plan); return }
|
|
3375
3423
|
if (!request.path) return
|
|
3376
3424
|
let path = receiptTabPath(request.path, cwd)
|
|
3377
3425
|
const existing = tabsRef.current.find((tab) => receiptPath(tab.path, cwd) === receiptPath(path, cwd))
|
|
@@ -3384,7 +3432,7 @@ export function EditorView(props) {
|
|
|
3384
3432
|
if (positioned && mdPreviewRef.current.has(path)) toggleMdPreview(path)
|
|
3385
3433
|
setError(null)
|
|
3386
3434
|
if (monacoErr) setMonacoErr(null)
|
|
3387
|
-
addTab(path, true)
|
|
3435
|
+
addTab(path, true, positioned ? false : request.preview)
|
|
3388
3436
|
if (positioned) stageFileAt(path, request.line ?? 1, request.column ?? 1)
|
|
3389
3437
|
const moved = receiptLoad && receiptPath(receiptLoad.path, receiptLoad.cwd) !== receiptPath(path, cwd)
|
|
3390
3438
|
if (path !== active || !(loadError || receiptLoad?.status === 'error' || moved)) return
|
|
@@ -3423,10 +3471,14 @@ export function EditorView(props) {
|
|
|
3423
3471
|
|
|
3424
3472
|
/** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Correlated actual readiness. */
|
|
3425
3473
|
function receiptReady(request) {
|
|
3474
|
+
if (request.plan) return request.sessionId === sessionId && planActive === request.plan.address
|
|
3475
|
+
&& planTabs.some((item) => item.address === request.plan.address && item.markdown === request.plan.markdown)
|
|
3476
|
+
&& planHostRef.current?.getAttribute('data-plan-address') === request.plan.address && !svnDiff
|
|
3426
3477
|
return receiptReadyFor(request, receiptView(request))
|
|
3427
3478
|
}
|
|
3428
3479
|
/** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Only its current load/render error. */
|
|
3429
3480
|
function receiptError(request) {
|
|
3481
|
+
if (request.plan) return planDocument(request.plan.address, request.plan) ? null : '计划内容或资源地址无效'
|
|
3430
3482
|
return receiptErrorFor(request, receiptView(request))
|
|
3431
3483
|
}
|
|
3432
3484
|
useOpenReceipt({ sessionId, open: openReceiptFile, ready: receiptReady, error: receiptError })
|
|
@@ -3514,11 +3566,20 @@ export function EditorView(props) {
|
|
|
3514
3566
|
// @author ddj 2026年09月09号
|
|
3515
3567
|
(dirtyMap[t.path] ? React.createElement('span', { className: 'edrv-tab-star', title: '未保存修改' }, '*') : null),
|
|
3516
3568
|
(t.pinned ? null : React.createElement('span', { className: 'edrv-tab-x', title: '关闭', onClick: (e) => { e.stopPropagation(); closeTab(t.path) } }, '×')))),
|
|
3569
|
+
planTabs.map((plan) => React.createElement('div', {
|
|
3570
|
+
key: plan.address, className: 'edrv-tab' + (plan.address === planActive ? ' edrv-tab-active' : ''),
|
|
3571
|
+
title: plan.title + '(计划预览 · 只读)', onClick: () => openPlanDoc(plan),
|
|
3572
|
+
}, React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 } }, plan.title),
|
|
3573
|
+
React.createElement('span', { className: 'edrv-tab-x', title: '关闭', onClick: (event) => closePlan(plan.address, event) }, '×'))),
|
|
3517
3574
|
(openInput
|
|
3518
3575
|
? React.createElement('input', { className: 'edrv-path-input', autoFocus: true, placeholder: '输入工作区相对/绝对路径,回车打开', value: pathDraft, onChange: (e) => setPathDraft(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter') openPath(); if (e.key === 'Escape') setOpenInput(false) } })
|
|
3519
3576
|
: React.createElement('button', { className: 'edrv-tab-add', title: '打开文件(输入路径)', onClick: () => setOpenInput(true) }, '+')))
|
|
3520
3577
|
|
|
3521
|
-
const
|
|
3578
|
+
const activePlan = planTabs.find((plan) => plan.address === planActive)
|
|
3579
|
+
const pathBar = activePlan ? React.createElement('div', { className: 'edrv-pathbar', title: activePlan.address },
|
|
3580
|
+
React.createElement('span', { className: 'edrv-pb-name' }, activePlan.title),
|
|
3581
|
+
React.createElement('span', { className: 'edrv-pb-meta' }, '计划预览 · 只读 · 审批请使用对话中的官方控件'))
|
|
3582
|
+
: React.createElement('div', { className: 'edrv-pathbar', title: active || '' },
|
|
3522
3583
|
React.createElement('span', { className: 'edrv-pb-name' }, active ? String(active).split(/[\\/]/).pop() : '未打开文件'),
|
|
3523
3584
|
React.createElement('span', { className: 'edrv-pb-full' }, active || '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开文件'),
|
|
3524
3585
|
(recentUpdate && !svnUpdateDialog && sameUpdateOwner(recentUpdate, sessionId, scope)
|
|
@@ -3681,7 +3742,17 @@ export function EditorView(props) {
|
|
|
3681
3742
|
retry ? React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: retry }, '重试') : null)
|
|
3682
3743
|
|
|
3683
3744
|
let body
|
|
3684
|
-
if (
|
|
3745
|
+
if (activePlan) {
|
|
3746
|
+
const labels = PLAN_MD_LABELS
|
|
3747
|
+
const text = activePlan.markdown
|
|
3748
|
+
const render = isComponentType(MarkdownText)
|
|
3749
|
+
? React.createElement(MarkdownDelegateProvider, {
|
|
3750
|
+
openFile: (path, options) => options?.line != null ? openFileAt(path, options.line) : openFile(path, false),
|
|
3751
|
+
}, React.createElement(MarkdownText, { text, streaming: false, labels, variant: 'body' }))
|
|
3752
|
+
: React.createElement('pre', { className: 'edrv-mdview-pre' }, text)
|
|
3753
|
+
body = React.createElement('div', { className: 'edrv-mdview', ref: planHostRef, 'data-plan-address': activePlan.address },
|
|
3754
|
+
React.createElement('div', { className: 'edrv-mdview-stage' }, React.createElement('div', { className: 'edrv-mdview-doc' }, render)))
|
|
3755
|
+
} else if (svnDiff) {
|
|
3685
3756
|
// 差异视图优先占位(覆盖编辑器主体):只读审阅态,关闭后回到原编辑器
|
|
3686
3757
|
body = React.createElement(SvnDiffPanel, {
|
|
3687
3758
|
key: 'edrv-svndiff:' + svnDiff.scheme + ':' + svnDiff.path,
|