dsh-vscode-mode 0.8.0 → 0.9.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/README.md +16 -2
- package/cordis.patch.yml +7 -3
- package/lib/client.js +1646 -121
- package/lib/client.js.map +1 -1
- package/lib/index.js +1141 -106
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/ai/inline.ts +19 -2
- package/src/ai/svnTriage.ts +150 -0
- package/src/capture.ts +225 -51
- package/src/client/addToConversation.ts +203 -1
- package/src/client/pendingNav.ts +141 -0
- package/src/client/sidebar/panels/FileExplorer.ts +2 -15
- package/src/client/sidebar/panels/SvnPanel.ts +682 -72
- package/src/client/sidebar/panels/svnListBudget.ts +58 -0
- package/src/client/state/records.ts +7 -1
- package/src/client/styles/editor.css +42 -2
- package/src/client/svnStatus.ts +206 -1
- package/src/client/ui/AiSettings.ts +47 -14
- package/src/client/ui/DiffBox.ts +1 -1
- package/src/client/ui/DiffLauncher.ts +6 -4
- package/src/client/ui/EditorView.ts +32 -4
- package/src/client/ui/SvnAiPlanDialog.ts +293 -0
- package/src/client/ui/icons.ts +19 -0
- package/src/compat.ts +25 -4
- package/src/fileOpenSettings.ts +128 -26
- package/src/index.ts +7 -4
- package/src/model.ts +2 -1
- package/src/remoteWorkspace.ts +177 -0
- package/src/rpc.ts +19 -5
- package/src/shared/ai.ts +12 -0
- package/src/shared/rpc.ts +16 -1
- package/src/shared/svn.ts +296 -10
- package/src/shared/types.ts +5 -1
- package/src/svn.ts +235 -13
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — AI 智能整理弹窗(11-ai-changelist-triage)。
|
|
4
|
+
*
|
|
5
|
+
* 双阶段单弹窗:
|
|
6
|
+
* - 预览(run = null):按**整理功能**划分三个分组卡(建议分组 changelist / 建议还原 revert /
|
|
7
|
+
* 建议忽略 svn:ignore),每段段级全选 + 逐项勾选确认;默认「分组勾选(可逆无副作用)、
|
|
8
|
+
* 还原/忽略不勾(有副作用)」,未勾选项一律不动。
|
|
9
|
+
* - 执行(run 非空):弹窗不关闭,切换为进度视图——阶段徽标 + 步骤计数 + 总进度条 +
|
|
10
|
+
* 逐步日志(✓/✗,失败红字);「暂停」在当前 RPC 返回后的步骤边界生效(svn 子进程不可
|
|
11
|
+
* 安全中断,如实标注「等待当前步骤完成…」),「继续」从断点续跑,「取消剩余」终止未执行
|
|
12
|
+
* 步骤(已执行不回滚)。
|
|
13
|
+
* Esc/遮罩仅在预览阶段生效(= 取消零副作用);执行阶段防误关。
|
|
14
|
+
* 作者 ddj 2026年09月23号
|
|
15
|
+
*/
|
|
16
|
+
import React from 'react'
|
|
17
|
+
import { ModalShell } from './ModalShell.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 预览勾选初值:分组默认全勾(changelist 可逆),还原/忽略默认不勾(有副作用)。
|
|
21
|
+
* @author ddj 2026年09月23号
|
|
22
|
+
* @param plan 三段方案
|
|
23
|
+
* @returns 勾选态(组级 + 逐项)
|
|
24
|
+
*/
|
|
25
|
+
function initPlanSel(plan) {
|
|
26
|
+
return {
|
|
27
|
+
groups: (plan.groups || []).map((group) => ({
|
|
28
|
+
name: group.name,
|
|
29
|
+
reason: group.reason || '',
|
|
30
|
+
checked: true,
|
|
31
|
+
paths: (group.paths || []).map((path) => ({ path, checked: true })),
|
|
32
|
+
})),
|
|
33
|
+
reverts: (plan.reverts || []).map((item) => ({ path: item.path, reason: item.reason || '', checked: false })),
|
|
34
|
+
ignores: (plan.ignores || []).map((item) => ({ path: item.path, reason: item.reason || '', checked: false })),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 勾选态 → 执行载荷(空段/空组剔除;保持勾选顺序)。
|
|
40
|
+
* @author ddj 2026年09月23号
|
|
41
|
+
* @param sel 勾选态
|
|
42
|
+
* @returns 执行载荷 { groups, reverts, ignores }
|
|
43
|
+
*/
|
|
44
|
+
function selectedOf(sel) {
|
|
45
|
+
const groups = []
|
|
46
|
+
for (const group of sel.groups) {
|
|
47
|
+
const paths = group.paths.filter((p) => p.checked).map((p) => p.path)
|
|
48
|
+
if (group.checked && paths.length) groups.push({ name: group.name, paths })
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
groups,
|
|
52
|
+
reverts: sel.reverts.filter((item) => item.checked).map((item) => item.path),
|
|
53
|
+
ignores: sel.ignores.filter((item) => item.checked).map((item) => item.path),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 执行载荷 → 已选动作总数(「执行所选」禁用判定)。 */
|
|
58
|
+
function selectedCount(selected) {
|
|
59
|
+
let n = selected.reverts.length + selected.ignores.length
|
|
60
|
+
for (const group of selected.groups) n += group.paths.length
|
|
61
|
+
return n
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 复选框元素(原生 input;仓库 UI 原语无 Checkbox,此处沿用补丁对话框的原生控件口径)。 */
|
|
65
|
+
function checkBoxEl(checked, onToggle, title) {
|
|
66
|
+
return React.createElement('input', {
|
|
67
|
+
type: 'checkbox',
|
|
68
|
+
checked,
|
|
69
|
+
title,
|
|
70
|
+
onChange: () => onToggle(),
|
|
71
|
+
onClick: (event) => event.stopPropagation(),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 段头(全选框 + 标题 + 计数 + 副标题)。 */
|
|
76
|
+
function sectionHeadEl(title, note, allChecked, onToggleAll, countText) {
|
|
77
|
+
return React.createElement('div', { className: 'edrv-svnplan-sechead' },
|
|
78
|
+
checkBoxEl(allChecked, onToggleAll, '全选/全不选'),
|
|
79
|
+
React.createElement('span', { className: 'edrv-svnplan-sectitle' }, title),
|
|
80
|
+
React.createElement('span', { className: 'edrv-svn-count' }, countText),
|
|
81
|
+
(note ? React.createElement('span', { className: 'edrv-svnplan-note' }, note) : null))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 阶段一 · 预览确认(按整理功能三分组)。
|
|
86
|
+
* @author ddj 2026年09月23号
|
|
87
|
+
* @param sel 勾选态
|
|
88
|
+
* @param setSel 勾选态更新
|
|
89
|
+
* @param open 组卡展开集合(组名)
|
|
90
|
+
* @param toggleOpen 组卡展开切换
|
|
91
|
+
* @returns 预览主体元素
|
|
92
|
+
*/
|
|
93
|
+
function previewBodyEl(sel, setSel, open, toggleOpen) {
|
|
94
|
+
/** 通用段更新(reverts/ignores 共用)。 */
|
|
95
|
+
const patchItems = (key, index, checked) => setSel((old) => ({
|
|
96
|
+
...old,
|
|
97
|
+
[key]: old[key].map((item, i) => (i === index ? { ...item, checked } : item)),
|
|
98
|
+
}))
|
|
99
|
+
const toggleItemsAll = (key, checked) => setSel((old) => ({
|
|
100
|
+
...old,
|
|
101
|
+
[key]: old[key].map((item) => ({ ...item, checked })),
|
|
102
|
+
}))
|
|
103
|
+
/** 组级/组内更新。 */
|
|
104
|
+
const patchGroup = (index, patch) => setSel((old) => ({
|
|
105
|
+
...old,
|
|
106
|
+
groups: old.groups.map((group, i) => (i === index ? { ...group, ...patch } : group)),
|
|
107
|
+
}))
|
|
108
|
+
const toggleGroupAll = (index, checked) => setSel((old) => ({
|
|
109
|
+
...old,
|
|
110
|
+
groups: old.groups.map((group, i) => (i === index
|
|
111
|
+
? { ...group, checked, paths: group.paths.map((p) => ({ ...p, checked })) }
|
|
112
|
+
: group)),
|
|
113
|
+
}))
|
|
114
|
+
|
|
115
|
+
const groupCards = sel.groups.map((group, index) => {
|
|
116
|
+
const done = group.paths.filter((p) => p.checked).length
|
|
117
|
+
const isOpen = open.has(group.name)
|
|
118
|
+
const fileRows = isOpen
|
|
119
|
+
? group.paths.map((item, pi) => React.createElement('div', { key: item.path, className: 'edrv-svnplan-item' },
|
|
120
|
+
checkBoxEl(item.checked, () => patchGroup(index, {
|
|
121
|
+
paths: group.paths.map((p, j) => (j === pi ? { ...p, checked: !p.checked } : p)),
|
|
122
|
+
}), item.path),
|
|
123
|
+
React.createElement('span', { className: 'edrv-svnplan-path', title: item.path }, item.path)))
|
|
124
|
+
: null
|
|
125
|
+
return React.createElement('div', { key: group.name, className: 'edrv-svnplan-card' },
|
|
126
|
+
React.createElement('div', { className: 'edrv-svnplan-row' },
|
|
127
|
+
checkBoxEl(group.checked, () => toggleGroupAll(index, !group.checked), '全选该组'),
|
|
128
|
+
React.createElement('span', {
|
|
129
|
+
className: 'edrv-svnplan-groupname',
|
|
130
|
+
title: '点击展开/收起成员',
|
|
131
|
+
onClick: () => toggleOpen(group.name),
|
|
132
|
+
}, (isOpen ? '▾ ' : '▸ ') + group.name),
|
|
133
|
+
React.createElement('span', { className: 'edrv-svn-count' }, done + '/' + group.paths.length + ' 项'),
|
|
134
|
+
(group.reason ? React.createElement('span', { className: 'edrv-svnplan-reason', title: group.reason }, group.reason) : null)),
|
|
135
|
+
fileRows)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const itemRowsOf = (key) => sel[key].map((item, index) => React.createElement('div', { key: item.path, className: 'edrv-svnplan-item' },
|
|
139
|
+
checkBoxEl(item.checked, () => patchItems(key, index, !item.checked), item.path),
|
|
140
|
+
React.createElement('span', { className: 'edrv-svnplan-path', title: item.path }, item.path),
|
|
141
|
+
(item.reason ? React.createElement('span', { className: 'edrv-svnplan-reason', title: item.reason }, item.reason) : null)))
|
|
142
|
+
|
|
143
|
+
// 段级全选:取反当前全选态(checkBoxEl 的 onToggle 不回传状态,须闭包捕获)
|
|
144
|
+
const groupsAll = sel.groups.length > 0 && sel.groups.every((g) => g.checked && g.paths.every((p) => p.checked))
|
|
145
|
+
const revertsAll = sel.reverts.length > 0 && sel.reverts.every((i) => i.checked)
|
|
146
|
+
const ignoresAll = sel.ignores.length > 0 && sel.ignores.every((i) => i.checked)
|
|
147
|
+
|
|
148
|
+
return React.createElement(React.Fragment, null,
|
|
149
|
+
React.createElement('div', { className: 'edrv-svnplan-sec' },
|
|
150
|
+
sectionHeadEl('建议分组(changelist)', '无副作用、可逆', groupsAll,
|
|
151
|
+
() => setSel((old) => ({
|
|
152
|
+
...old,
|
|
153
|
+
groups: old.groups.map((group) => ({ ...group, checked: !groupsAll, paths: group.paths.map((p) => ({ ...p, checked: !groupsAll })) })),
|
|
154
|
+
})), sel.groups.length + ' 组'),
|
|
155
|
+
React.createElement('div', { className: 'edrv-svnplan-list' }, groupCards)),
|
|
156
|
+
React.createElement('div', { className: 'edrv-svnplan-sec' },
|
|
157
|
+
sectionHeadEl('建议还原(revert)', '放弃本地改动,默认不勾', revertsAll,
|
|
158
|
+
() => toggleItemsAll('reverts', !revertsAll), sel.reverts.length + ' 项'),
|
|
159
|
+
React.createElement('div', { className: 'edrv-svnplan-list' }, itemRowsOf('reverts'))),
|
|
160
|
+
React.createElement('div', { className: 'edrv-svnplan-sec' },
|
|
161
|
+
sectionHeadEl('建议忽略(svn:ignore)', '将写入 svn:ignore 属性,随提交进仓库,默认不勾', ignoresAll,
|
|
162
|
+
() => toggleItemsAll('ignores', !ignoresAll), sel.ignores.length + ' 项'),
|
|
163
|
+
React.createElement('div', { className: 'edrv-svnplan-list' }, itemRowsOf('ignores'))))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 阶段二 · 执行进度(阶段徽标 + 计数 + 进度条 + 逐步日志)。
|
|
168
|
+
* @author ddj 2026年09月23号
|
|
169
|
+
* @param run 执行状态
|
|
170
|
+
* @returns 进度主体元素
|
|
171
|
+
*/
|
|
172
|
+
function progressBodyEl(run) {
|
|
173
|
+
const pct = run.total > 0 ? Math.floor((run.index / run.total) * 100) : 0
|
|
174
|
+
const statusText = run.status === 'pausing' ? '等待当前步骤完成…(暂停在步骤边界生效)'
|
|
175
|
+
: run.status === 'paused' ? '已暂停'
|
|
176
|
+
: run.status === 'cancelling' ? '正在取消剩余步骤…'
|
|
177
|
+
: run.status === 'done' ? (run.cancelled ? '已取消剩余步骤' : '执行完毕')
|
|
178
|
+
: '执行中…'
|
|
179
|
+
const logRows = run.log.map((entry, index) => React.createElement('div', {
|
|
180
|
+
key: index,
|
|
181
|
+
className: 'edrv-svnplan-log' + (entry.ok ? '' : ' edrv-svnplan-log-bad'),
|
|
182
|
+
}, (entry.ok ? '✓ ' : '✗ ') + entry.text))
|
|
183
|
+
return React.createElement(React.Fragment, null,
|
|
184
|
+
React.createElement('div', { className: 'edrv-svnplan-stage' },
|
|
185
|
+
React.createElement('span', { className: 'edrv-svnplan-badge' }, statusText),
|
|
186
|
+
(run.cur ? React.createElement('span', { className: 'edrv-svnplan-badge edrv-svnplan-badge-cur' }, run.cur) : null),
|
|
187
|
+
React.createElement('span', { className: 'edrv-svn-count' }, run.index + '/' + run.total + ' 步')),
|
|
188
|
+
React.createElement('div', { className: 'edrv-svnplan-bar' },
|
|
189
|
+
React.createElement('div', { className: 'edrv-svnplan-bar-in', style: { width: pct + '%' } })),
|
|
190
|
+
React.createElement('div', { className: 'edrv-svnplan-logwrap' }, logRows))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* AI 智能整理弹窗(预览 + 进度双阶段)。
|
|
195
|
+
* @author ddj 2026年09月23号
|
|
196
|
+
* @param props.plan 三段方案(host 已归一)
|
|
197
|
+
* @param props.meta 分析元信息 { entriesCount, diffIncluded, dropped, model, source? }(source='agent' = 混合通道)
|
|
198
|
+
* @param props.run 执行状态;null = 预览阶段(shape 见 SvnPanel runPlanSteps)
|
|
199
|
+
* @param props.onExecute 预览确认(选中载荷 → 启动执行引擎)
|
|
200
|
+
* @param props.onPause 暂停(步骤边界生效)
|
|
201
|
+
* @param props.onResume 继续
|
|
202
|
+
* @param props.onCancelRest 取消剩余
|
|
203
|
+
* @param props.onClose 关闭(预览 = 取消零副作用;执行阶段按钮仅在结束后可用)
|
|
204
|
+
* @returns 弹窗元素
|
|
205
|
+
*/
|
|
206
|
+
export function SvnAiPlanDialog(props) {
|
|
207
|
+
const { plan, meta, run, onExecute, onPause, onResume, onCancelRest, onClose } = props
|
|
208
|
+
const [sel, setSel] = React.useState(() => initPlanSel(plan))
|
|
209
|
+
const [open, setOpen] = React.useState(() => new Set())
|
|
210
|
+
const selected = selectedOf(sel)
|
|
211
|
+
const count = selectedCount(selected)
|
|
212
|
+
|
|
213
|
+
/** 组卡展开切换(纯 UI 态)。 */
|
|
214
|
+
const toggleOpen = (name) => {
|
|
215
|
+
const next = new Set(open)
|
|
216
|
+
if (next.has(name)) next.delete(name)
|
|
217
|
+
else next.add(name)
|
|
218
|
+
setOpen(next)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const running = run !== null && run.status !== 'done'
|
|
222
|
+
const header = React.createElement('div', { className: 'edrv-svnplan-head' },
|
|
223
|
+
React.createElement('h3', { className: 'edrv-svnlog-title' }, 'AI 智能整理 SVN 变更'),
|
|
224
|
+
// 混合通道来源标注(agent 深度分析 vs 快速通道直调)
|
|
225
|
+
(meta?.source === 'agent'
|
|
226
|
+
? React.createElement('span', { className: 'edrv-svnplan-badge', title: '会话 AI 助手深度分析(codegraph/读文件)产出' }, 'AI 助手深度分析')
|
|
227
|
+
: null),
|
|
228
|
+
(meta?.model ? React.createElement('span', { className: 'edrv-svn-count', title: '分析模型' }, meta.model) : null),
|
|
229
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
230
|
+
React.createElement('button', {
|
|
231
|
+
className: 'edrv-svn-act',
|
|
232
|
+
title: running ? '执行中不可关闭(可取消剩余)' : '关闭',
|
|
233
|
+
disabled: running,
|
|
234
|
+
onClick: onClose,
|
|
235
|
+
}, '✕'))
|
|
236
|
+
|
|
237
|
+
const metaEl = React.createElement('div', { className: 'edrv-svnplan-meta' },
|
|
238
|
+
React.createElement('span', { className: 'edrv-svn-count' }, '分析条目 ' + (meta?.entriesCount ?? 0) + ' 项'),
|
|
239
|
+
((meta?.dropped ?? 0) > 0 ? React.createElement('span', { className: 'edrv-svn-count' }, '已丢弃 AI 无效建议 ' + meta.dropped + ' 项') : null),
|
|
240
|
+
(meta?.diffIncluded === false
|
|
241
|
+
? React.createElement('span', { className: 'edrv-svnplan-note' }, '未含 diff 上下文,分析精度有限')
|
|
242
|
+
: null))
|
|
243
|
+
|
|
244
|
+
let body = null
|
|
245
|
+
let footer = null
|
|
246
|
+
if (run === null) {
|
|
247
|
+
body = previewBodyEl(sel, setSel, open, toggleOpen)
|
|
248
|
+
footer = React.createElement('div', { className: 'edrv-svnplan-foot' },
|
|
249
|
+
React.createElement('span', { className: 'edrv-svn-count' }, '已选 ' + count + ' 项'),
|
|
250
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
251
|
+
React.createElement('button', { className: 'edrv-svn-act', title: '不执行任何动作', onClick: onClose }, '取消'),
|
|
252
|
+
React.createElement('button', {
|
|
253
|
+
className: 'edrv-svn-act edrv-svnplan-primary',
|
|
254
|
+
title: '执行全部勾选项(未勾选不动)',
|
|
255
|
+
disabled: count === 0,
|
|
256
|
+
onClick: () => onExecute(selected),
|
|
257
|
+
}, '执行所选(' + count + ' 项)'))
|
|
258
|
+
} else {
|
|
259
|
+
body = progressBodyEl(run)
|
|
260
|
+
footer = React.createElement('div', { className: 'edrv-svnplan-foot' },
|
|
261
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
262
|
+
(run.status === 'done'
|
|
263
|
+
? React.createElement('button', {
|
|
264
|
+
className: 'edrv-svn-act edrv-svnplan-primary',
|
|
265
|
+
title: '关闭弹窗并刷新变更列表',
|
|
266
|
+
onClick: onClose,
|
|
267
|
+
}, '关闭并刷新')
|
|
268
|
+
: React.createElement(React.Fragment, null,
|
|
269
|
+
React.createElement('button', {
|
|
270
|
+
className: 'edrv-svn-act',
|
|
271
|
+
title: run.status === 'running' || run.status === 'pausing'
|
|
272
|
+
? '暂停(当前步骤完成后生效)'
|
|
273
|
+
: '从断点继续执行',
|
|
274
|
+
disabled: run.status === 'pausing' || run.status === 'cancelling',
|
|
275
|
+
onClick: run.status === 'paused' ? onResume : onPause,
|
|
276
|
+
}, run.status === 'paused' ? '继续' : '暂停'),
|
|
277
|
+
React.createElement('button', {
|
|
278
|
+
className: 'edrv-svn-act edrv-svn-act-danger',
|
|
279
|
+
title: '终止未执行步骤(已执行部分不回滚)',
|
|
280
|
+
disabled: run.status === 'cancelling',
|
|
281
|
+
onClick: onCancelRest,
|
|
282
|
+
}, '取消剩余'))))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return React.createElement(ModalShell, {
|
|
286
|
+
key: 'edrv-svn-plan',
|
|
287
|
+
dialogClass: 'edrv-svnplan-dialog',
|
|
288
|
+
width: 'min(680px, calc(100vw - 48px))',
|
|
289
|
+
closeOnMask: !running,
|
|
290
|
+
closeOnEsc: !running,
|
|
291
|
+
onClose,
|
|
292
|
+
}, header, metaEl, body, footer)
|
|
293
|
+
}
|
package/src/client/ui/icons.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import React from 'react'
|
|
12
12
|
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
|
|
13
|
+
import { classifyFileType, FileTypeIcon } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
13
14
|
import type { EdrvIconProps } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
14
15
|
|
|
15
16
|
/** 图标组件形状(与垫片 EdrvIconProps 兼容)。 */
|
|
@@ -70,3 +71,21 @@ export const IconBranchOutline16 = iconOf('IconBranchOutline16', 'IconBranchOutl
|
|
|
70
71
|
|
|
71
72
|
// 两代同名存活的原语:直通再导出(0.1.6/0.1.7 实证均在)
|
|
72
73
|
export { FileTypeIcon, classifyFileType } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 文件图标元素(官方 FileTypeIcon + classifyFileType 分类,未知类型为 other)。
|
|
77
|
+
* 文件树与 SVN 变更行共用(原为 FileExplorer 私有副本,上移共享防两处漂移)。
|
|
78
|
+
* @author ddj 2026年09月23号
|
|
79
|
+
* @param name 文件名(含扩展名;目录由调用方走目录图标)
|
|
80
|
+
* @returns 图标元素;原语缺失或分类异常返回 null
|
|
81
|
+
*/
|
|
82
|
+
export function fileIconEl(name: string): React.ReactElement | null {
|
|
83
|
+
if (typeof FileTypeIcon !== 'function' || typeof classifyFileType !== 'function') return null
|
|
84
|
+
try {
|
|
85
|
+
// FileTypeIcon 签名为 (props) => ReactNode(非 ReactElement),createElement 重载不认——
|
|
86
|
+
// 纯函数组件直接调用并收窄返回类型(与 FileExplorer 原 createElement 等效)。
|
|
87
|
+
return FileTypeIcon({ kind: classifyFileType(String(name ?? '')), size: 16, className: 'edrv-tree-icon' }) as React.ReactElement
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/compat.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { readFileSync } from 'node:fs'
|
|
|
10
10
|
import { dirname, join } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { entriesOf } from './mcp.js'
|
|
13
|
-
import { loadSettingsDeps, schemaLibName, settingsInstallNote, settingsInstallStrategy } from './fileOpenSettings.js'
|
|
13
|
+
import { loadSettingsDeps, schemaLibName, settingsInstallNote, settingsInstallStrategy, configVolatileState } from './fileOpenSettings.js'
|
|
14
14
|
import { readDevForm } from './devForm.js'
|
|
15
15
|
import { compareDshVersions, detectDshVersion, familyLabel, parseDshVersion } from './dshVersion.js'
|
|
16
16
|
import { SKILL_PREFIXES, skillGroupState } from './skills.js'
|
|
@@ -133,9 +133,12 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
|
|
136
|
-
const TESTED_DSH_MAX = '0.1.7-alpha.
|
|
136
|
+
const TESTED_DSH_MAX = '0.1.7-alpha.2'
|
|
137
137
|
|
|
138
|
-
/**
|
|
138
|
+
/** Config volatile 标记起效的版本线(此前设置走 section 安装,与 volatile 无关)。 */
|
|
139
|
+
const CONFIG_VOLATILE_MIN = '0.1.7-alpha.1'
|
|
140
|
+
|
|
141
|
+
/** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略 + Config volatile 标记。 */
|
|
139
142
|
export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
|
140
143
|
const adapters: CompatAdapter[] = []
|
|
141
144
|
const parsed = parseDshVersion(dshVersion)
|
|
@@ -152,12 +155,24 @@ export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
|
|
152
155
|
active: strategy === 'legacy' || strategy === 'service' || strategy === 'forms',
|
|
153
156
|
note: settingsInstallNote(),
|
|
154
157
|
})
|
|
158
|
+
// 追加在末尾(既有下标有断言);仅 Config 按 volatile 语义构建过才展示该行
|
|
159
|
+
const volatileState = configVolatileState()
|
|
160
|
+
if (volatileState.requested) {
|
|
161
|
+
const volatileOk = volatileState.total > 0 && volatileState.marked === volatileState.total
|
|
162
|
+
adapters.push({
|
|
163
|
+
name: 'Config volatile 字段(0.1.7 设置页)',
|
|
164
|
+
active: volatileOk,
|
|
165
|
+
note: volatileOk
|
|
166
|
+
? volatileState.marked + '/' + volatileState.total + ' 已标记(SettingsForms 可下发本插件 ns)'
|
|
167
|
+
: volatileState.marked + '/' + volatileState.total + ' 已标记:schemastery 依赖过旧无 .volatile(),0.1.7 设置页不可用',
|
|
168
|
+
})
|
|
169
|
+
}
|
|
155
170
|
return adapters
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
/**
|
|
159
174
|
* 构建完整兼容性报告(RPC 与启动日志共用)。
|
|
160
|
-
* @author ddj 2026年08月24号 / 2026年09月02号
|
|
175
|
+
* @author ddj 2026年08月24号 / 2026年09月02号 / 2026年09月23号
|
|
161
176
|
* @param ctx DSH host 上下文
|
|
162
177
|
* @param options 测试注入:depsAvailable 跳过动态导入、version 固定插件版本、dshVersion 固定 DSH 版本
|
|
163
178
|
* @returns 兼容性报告
|
|
@@ -180,6 +195,12 @@ export async function buildReport(
|
|
|
180
195
|
if (parsed && testedMax && compareDshVersions(parsed, testedMax) > 0) {
|
|
181
196
|
warnings.push('DSH ' + dshVersion + ' 高于已实测版本(' + TESTED_DSH_MAX + '):设置 API 按能力探测运行,异常时请回报适配矩阵')
|
|
182
197
|
}
|
|
198
|
+
// 0.1.7 线上 Config 未完成 volatile 标记 = 设置页不可用(ns 不下发),显式告警而非静默
|
|
199
|
+
const volatileState = configVolatileState()
|
|
200
|
+
const volatileMin = parseDshVersion(CONFIG_VOLATILE_MIN)
|
|
201
|
+
if (volatileState.requested && volatileState.marked < volatileState.total && parsed && volatileMin && compareDshVersions(parsed, volatileMin) >= 0) {
|
|
202
|
+
warnings.push('插件 Config volatile 标记不完整(' + volatileState.marked + '/' + volatileState.total + '):@deepseek-ai/schemastery 依赖过旧,0.1.7 设置页不可用(重装插件依赖后重启 DSH)')
|
|
203
|
+
}
|
|
183
204
|
for (const guard of guards) {
|
|
184
205
|
if (!guard.active && guard.note) warnings.push(guard.note)
|
|
185
206
|
}
|
package/src/fileOpenSettings.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 插件 Config schema,见 runSettingsInstall 四策略:legacy/service/forms/none)。
|
|
7
7
|
* 缺失/任一策略失败时插件仍可加载(fileOpenTool 降级为配置值,compat 报告可见),
|
|
8
8
|
* 全程 try/catch,不产生未捕获 rejection。
|
|
9
|
-
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号 / 2026年09月22号
|
|
9
|
+
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号 / 2026年09月22号 / 2026年09月23号
|
|
10
10
|
*/
|
|
11
11
|
import { createRequire } from 'node:module'
|
|
12
12
|
import { pathToFileURL } from 'node:url'
|
|
@@ -24,12 +24,15 @@ export const FILE_OPEN_DEFAULT = 'auto'
|
|
|
24
24
|
export interface FileOpenSettings { fileOpenTool: string; integrationBaseUrl: string; svnPath: string; tortoisePath: string }
|
|
25
25
|
export interface FileOpenSettingsState { value: string; revision?: number; update: (value: string, expectedRevision?: number) => Promise<void> }
|
|
26
26
|
|
|
27
|
-
/** AI
|
|
28
|
-
export const AI_CONFIG_DEFAULT: { enabled: boolean; provider: string; model: string; effort: string } = {
|
|
27
|
+
/** AI 配置默认值(补全默认关;路由空 = 自动;档位空 = 跟随模型默认;任务模型空 = 跟随补全配置)。 */
|
|
28
|
+
export const AI_CONFIG_DEFAULT: { enabled: boolean; provider: string; model: string; effort: string; taskProvider: string; taskModel: string; taskEffort: string } = {
|
|
29
29
|
enabled: false,
|
|
30
30
|
provider: '',
|
|
31
31
|
model: '',
|
|
32
32
|
effort: '',
|
|
33
|
+
taskProvider: '',
|
|
34
|
+
taskModel: '',
|
|
35
|
+
taskEffort: '',
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
type SettingsProvider = {
|
|
@@ -405,29 +408,118 @@ export async function updateSection(provider: SettingsProvider | undefined, ns:
|
|
|
405
408
|
* 字段集 = fileOpenTool/keybindings/sidebarMinWidth/maxOpenEditors/integrationBaseUrl/
|
|
406
409
|
* aiInline/aiProvider/aiModel/aiEffort/svnPath/tortoisePath/nativeOpenExts,全部带默认值
|
|
407
410
|
* (Config 启动校验在 undefined/空配置下自动填充,rc/alpha 两代 cordis 均通过)。
|
|
408
|
-
*
|
|
411
|
+
* options.volatile:DSH 0.1.7 线的 Config 导出专用——SettingsForms.describe 只下发
|
|
412
|
+
* 含 volatile 字段的 entry(volatileForm 门槛),不标记则 ns 永不出现在 describe、
|
|
413
|
+
* client configForms 恒 unavailable;且字段须位于固定 object 路径(keybindings 整个
|
|
414
|
+
* object 标记、子键不标)。section 安装路径(legacy/service)**不标**:rc/0.1.6 的
|
|
415
|
+
* register→resolve 无该门槛,标了反而让 scope.get() 返回引用污染读取。
|
|
416
|
+
* 标记经 markVolatile 能力守卫:z 无 .volatile()(旧 schemastery)或调用抛错时保持
|
|
417
|
+
* 原字段并记录观测态,模块加载绝不抛错(降级=维持现状,兼容性报告可见)。
|
|
418
|
+
* @author ddj 2026年09月22号(2026年09月23号 增补 options.volatile 与观测)
|
|
409
419
|
* @param z schemastery 命名空间(静态 import 或动态加载均可)
|
|
420
|
+
* @param options volatile:是否按 0.1.7 Config 语义标记字段
|
|
410
421
|
* @returns schemastery object schema
|
|
411
422
|
*/
|
|
412
|
-
export function buildSettingsSchema(z: SettingsDeps['z']): unknown {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
423
|
+
export function buildSettingsSchema(z: SettingsDeps['z'], options?: { volatile?: boolean }): unknown {
|
|
424
|
+
const wantVolatile = options?.volatile === true
|
|
425
|
+
let marked = 0
|
|
426
|
+
const withVol = (field: unknown): unknown => {
|
|
427
|
+
if (!wantVolatile) return field
|
|
428
|
+
const result = markVolatile(field)
|
|
429
|
+
if (result.marked) marked += 1
|
|
430
|
+
return result.field
|
|
431
|
+
}
|
|
432
|
+
const shape: Record<string, unknown> = {
|
|
433
|
+
fileOpenTool: withVol(z.string().default(FILE_OPEN_DEFAULT)),
|
|
434
|
+
keybindings: withVol(z.object(keybindingsShape(z)).default({ ...KEYBINDING_DEFAULTS })),
|
|
435
|
+
sidebarMinWidth: withVol(z.number().default(300)),
|
|
417
436
|
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
418
|
-
maxOpenEditors: z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
419
|
-
integrationBaseUrl: z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
437
|
+
maxOpenEditors: withVol(z.number().default(EDITOR_LIMIT_DEFAULT)),
|
|
438
|
+
integrationBaseUrl: withVol(z.string().default(INTEGRATION_BASE_DEFAULT)),
|
|
420
439
|
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
421
|
-
aiInline: z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
|
422
|
-
aiProvider: z.string().default(AI_CONFIG_DEFAULT.provider),
|
|
423
|
-
aiModel: z.string().default(AI_CONFIG_DEFAULT.model),
|
|
424
|
-
aiEffort: z.string().default(AI_CONFIG_DEFAULT.effort),
|
|
440
|
+
aiInline: withVol(z.boolean().default(AI_CONFIG_DEFAULT.enabled)),
|
|
441
|
+
aiProvider: withVol(z.string().default(AI_CONFIG_DEFAULT.provider)),
|
|
442
|
+
aiModel: withVol(z.string().default(AI_CONFIG_DEFAULT.model)),
|
|
443
|
+
aiEffort: withVol(z.string().default(AI_CONFIG_DEFAULT.effort)),
|
|
444
|
+
// AI 任务模型(非补全场景如 AI 智能整理;空 = 跟随补全配置)
|
|
445
|
+
aiTaskProvider: withVol(z.string().default(AI_CONFIG_DEFAULT.taskProvider)),
|
|
446
|
+
aiTaskModel: withVol(z.string().default(AI_CONFIG_DEFAULT.taskModel)),
|
|
447
|
+
aiTaskEffort: withVol(z.string().default(AI_CONFIG_DEFAULT.taskEffort)),
|
|
425
448
|
// SVN 能力:svn CLI 覆盖(空 = PATH)与 TortoiseSVN 目录(Windows 过渡增强)
|
|
426
|
-
svnPath: z.string().default(''),
|
|
427
|
-
tortoisePath: z.string().default(TORTOISE_DIR_DEFAULT),
|
|
449
|
+
svnPath: withVol(z.string().default('')),
|
|
450
|
+
tortoisePath: withVol(z.string().default(TORTOISE_DIR_DEFAULT)),
|
|
428
451
|
// 原生打开范围(逗号分隔后缀;默认 = 让位清单并集,csv/tsv 决策见 shared/nativeOpen.ts)
|
|
429
|
-
nativeOpenExts: z.string().default(DEFAULT_NATIVE_CSV),
|
|
430
|
-
}
|
|
452
|
+
nativeOpenExts: withVol(z.string().default(DEFAULT_NATIVE_CSV)),
|
|
453
|
+
}
|
|
454
|
+
if (wantVolatile) configVolatile = { requested: true, marked, total: Object.keys(shape).length }
|
|
455
|
+
return z.object(shape)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Config volatile 标记观测(构建期记录;兼容报告读取,测试可复位)。 */
|
|
459
|
+
export interface ConfigVolatileState { requested: boolean; marked: number; total: number }
|
|
460
|
+
|
|
461
|
+
let configVolatile: ConfigVolatileState = { requested: false, marked: 0, total: 0 }
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 读取 Config volatile 标记观测(0.1.7 设置页可用性的构建期判据)。
|
|
465
|
+
* @author ddj 2026年09月23号
|
|
466
|
+
* @returns 观测态(requested=是否按 Config 语义构建;marked/total=成功标记字段数)
|
|
467
|
+
*/
|
|
468
|
+
export function configVolatileState(): ConfigVolatileState {
|
|
469
|
+
return configVolatile
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 复位 volatile 标记观测(测试隔离用)。
|
|
474
|
+
* @author ddj 2026年09月23号
|
|
475
|
+
*/
|
|
476
|
+
export function resetConfigVolatileState(): void {
|
|
477
|
+
configVolatile = { requested: false, marked: 0, total: 0 }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 为已完成 default 链的 schema 字段追加 volatile 标记(能力守卫)。
|
|
482
|
+
* @author ddj 2026年09月23号
|
|
483
|
+
* @param field schema 字段(两代 schemastery 兼容面,可能没有 volatile 方法)
|
|
484
|
+
* @returns 处理后字段与是否成功标记
|
|
485
|
+
*/
|
|
486
|
+
function markVolatile(field: unknown): { field: unknown; marked: boolean } {
|
|
487
|
+
const target = field as { volatile?: () => unknown } | null | undefined
|
|
488
|
+
if (!target || typeof target.volatile !== 'function') return { field, marked: false }
|
|
489
|
+
try {
|
|
490
|
+
return { field: target.volatile(), marked: true }
|
|
491
|
+
} catch {
|
|
492
|
+
return { field, marked: false }
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** cosmokit volatile 引用写协议符号(Symbol.for 跨拷贝一致,同 cosmokit.isVolatile 判据)。 */
|
|
497
|
+
const VOLATILE_WRITE = Symbol.for('cosmokit.volatile.write')
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 解引用 0.1.7 volatile 配置引用(普通值直传)。
|
|
501
|
+
* cordis resolveConfig 会把 .volatile() 字段解析成稳定引用,直接读会得到引用对象;
|
|
502
|
+
* 不解引用则配置回退值全部落入默认值分支。
|
|
503
|
+
* @author ddj 2026年09月23号
|
|
504
|
+
* @param value 配置字段原始值
|
|
505
|
+
* @returns 引用的当前快照,或原值
|
|
506
|
+
*/
|
|
507
|
+
export function unref(value: unknown): unknown {
|
|
508
|
+
if (value !== null && typeof value === 'object' && VOLATILE_WRITE in (value as object)) {
|
|
509
|
+
return (value as { get: () => unknown }).get()
|
|
510
|
+
}
|
|
511
|
+
return value
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* 读插件组合配置字段并解引用(配置读取唯一入口)。
|
|
516
|
+
* @author ddj 2026年09月23号
|
|
517
|
+
* @param config 插件组合配置(apply 收到的 Config 值)
|
|
518
|
+
* @param key 字段名
|
|
519
|
+
* @returns 字段值(引用已解包;缺失为 undefined)
|
|
520
|
+
*/
|
|
521
|
+
export function configField(config: unknown, key: string): unknown {
|
|
522
|
+
return unref((config as Record<string, unknown> | null | undefined)?.[key])
|
|
431
523
|
}
|
|
432
524
|
|
|
433
525
|
function normalizeValue(value: unknown): string {
|
|
@@ -436,24 +528,24 @@ function normalizeValue(value: unknown): string {
|
|
|
436
528
|
}
|
|
437
529
|
|
|
438
530
|
function configValue(config: unknown): string {
|
|
439
|
-
return normalizeValue((config
|
|
531
|
+
return normalizeValue(configField(config, 'fileOpenTool'))
|
|
440
532
|
}
|
|
441
533
|
|
|
442
534
|
/** 配置/设置里的深链基址(缺省/非法回退默认值)。 */
|
|
443
535
|
function baseValueOf(config: unknown): string {
|
|
444
|
-
const raw = (config
|
|
536
|
+
const raw = configField(config, 'integrationBaseUrl')
|
|
445
537
|
return typeof raw === 'string' && raw.trim() ? raw.trim() : INTEGRATION_BASE_DEFAULT
|
|
446
538
|
}
|
|
447
539
|
|
|
448
540
|
/** 配置/设置里的 svn CLI 覆盖(空 = 从 PATH 解析 'svn')。 */
|
|
449
541
|
function svnPathValueOf(config: unknown): string {
|
|
450
|
-
const raw = (config
|
|
542
|
+
const raw = configField(config, 'svnPath')
|
|
451
543
|
return typeof raw === 'string' ? raw.trim() : ''
|
|
452
544
|
}
|
|
453
545
|
|
|
454
546
|
/** 配置/设置里的 TortoiseSVN 目录(空 = 默认安装目录)。 */
|
|
455
547
|
function tortoiseDirValueOf(config: unknown): string {
|
|
456
|
-
const raw = (config
|
|
548
|
+
const raw = configField(config, 'tortoisePath')
|
|
457
549
|
return typeof raw === 'string' && raw.trim() ? raw.trim() : TORTOISE_DIR_DEFAULT
|
|
458
550
|
}
|
|
459
551
|
|
|
@@ -490,13 +582,16 @@ export async function installOpenSettingsSection(
|
|
|
490
582
|
}
|
|
491
583
|
|
|
492
584
|
/** AI 配置脏值读取(settings 未就绪时回退默认)。 */
|
|
493
|
-
function aiValueOf(stored: unknown): { enabled: boolean; provider: string; model: string; effort: string } {
|
|
585
|
+
function aiValueOf(stored: unknown): { enabled: boolean; provider: string; model: string; effort: string; taskProvider: string; taskModel: string; taskEffort: string } {
|
|
494
586
|
const raw = (stored ?? {}) as Record<string, unknown>
|
|
495
587
|
return {
|
|
496
588
|
enabled: raw.aiInline === true,
|
|
497
589
|
provider: typeof raw.aiProvider === 'string' ? raw.aiProvider : AI_CONFIG_DEFAULT.provider,
|
|
498
590
|
model: typeof raw.aiModel === 'string' ? raw.aiModel : AI_CONFIG_DEFAULT.model,
|
|
499
591
|
effort: typeof raw.aiEffort === 'string' ? raw.aiEffort : AI_CONFIG_DEFAULT.effort,
|
|
592
|
+
taskProvider: typeof raw.aiTaskProvider === 'string' ? raw.aiTaskProvider : AI_CONFIG_DEFAULT.taskProvider,
|
|
593
|
+
taskModel: typeof raw.aiTaskModel === 'string' ? raw.aiTaskModel : AI_CONFIG_DEFAULT.taskModel,
|
|
594
|
+
taskEffort: typeof raw.aiTaskEffort === 'string' ? raw.aiTaskEffort : AI_CONFIG_DEFAULT.taskEffort,
|
|
500
595
|
}
|
|
501
596
|
}
|
|
502
597
|
|
|
@@ -555,8 +650,12 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
555
650
|
})
|
|
556
651
|
ctx.inject?.(['settings'], (settingsCtx: Ctx) => {
|
|
557
652
|
provider = settingsCtx.get('settings')
|
|
558
|
-
|
|
559
|
-
|
|
653
|
+
// 初值直读:settings/document-updated 首事件可能先于本订阅建立,只靠事件会漏初值
|
|
654
|
+
const stored = storedValue()
|
|
655
|
+
const section = stored as { fileOpenTool?: unknown } | undefined
|
|
656
|
+
if (section?.fileOpenTool !== undefined) notify(section.fileOpenTool)
|
|
657
|
+
aiCurrent = stored !== undefined ? aiValueOf(stored) : aiCurrent
|
|
658
|
+
svnSync(stored)
|
|
560
659
|
syncRevision()
|
|
561
660
|
})
|
|
562
661
|
|
|
@@ -580,6 +679,9 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
580
679
|
if (patch.provider !== undefined) { stored.provider = patch.provider; body.aiProvider = patch.provider }
|
|
581
680
|
if (patch.model !== undefined) { stored.model = patch.model; body.aiModel = patch.model }
|
|
582
681
|
if (patch.effort !== undefined) { stored.effort = patch.effort; body.aiEffort = patch.effort }
|
|
682
|
+
if (patch.taskProvider !== undefined) { stored.taskProvider = patch.taskProvider; body.aiTaskProvider = patch.taskProvider }
|
|
683
|
+
if (patch.taskModel !== undefined) { stored.taskModel = patch.taskModel; body.aiTaskModel = patch.taskModel }
|
|
684
|
+
if (patch.taskEffort !== undefined) { stored.taskEffort = patch.taskEffort; body.aiTaskEffort = patch.taskEffort }
|
|
583
685
|
const result = await updateSection(provider, FILE_OPEN_SETTINGS_NS, body, expectedRevision)
|
|
584
686
|
if (!result.ok) {
|
|
585
687
|
// settings 不可用/写入失败:内存态生效(重启回落默认),与 fileOpenTool 降级语义一致
|
package/src/index.ts
CHANGED
|
@@ -44,11 +44,14 @@ export const inject = ['sessions', 'fs', 'webServer', 'loader', 'tools', 'worksp
|
|
|
44
44
|
* 字段集与 installOpenSettingsSection 的 section schema 同源(buildSettingsSchema),
|
|
45
45
|
* 全字段带默认值——undefined/空配置经 schemastery 校验自动填充(rc/alpha 两代
|
|
46
46
|
* cordis 均读该导出,实测 4.0.0-rc.8 与 0.1.7 行为一致,校验必过)。
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* volatile: true —— 0.1.7 的 SettingsForms.describe 只下发含 volatile 字段的 entry
|
|
48
|
+
* (volatileForm 门槛),漏标则 ns 不出现 → client configForms 恒 unavailable →
|
|
49
|
+
* 设置页「设置服务暂不可用」;同时 volatile 字段由 Loader 就地提交免重载。
|
|
50
|
+
* 运行时解析:@deepseek-ai/schemastery 经插件自身 dependencies(link/dev/npm 安装
|
|
51
|
+
* 三形态一致,钉 ~3.18.4 —— 3.18.1 起才有 .volatile(),低版本经 markVolatile 守卫降级)。
|
|
52
|
+
* @author ddj 2026年09月22号(2026年09月23号 增补 volatile 标记)
|
|
50
53
|
*/
|
|
51
|
-
export const Config = buildSettingsSchema(z as unknown as SettingsDeps['z'])
|
|
54
|
+
export const Config = buildSettingsSchema(z as unknown as SettingsDeps['z'], { volatile: true })
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* 装配插件:挂事件监听、注册路由、安装兼容层。
|
package/src/model.ts
CHANGED
|
@@ -32,7 +32,8 @@ export function normalizeRecord(raw: unknown): DiffRecord | null {
|
|
|
32
32
|
}
|
|
33
33
|
})
|
|
34
34
|
: []
|
|
35
|
-
|
|
35
|
+
// 白名单保留已知工具名,未知/缺失回退 'edit'(旧 sidecar 语义不变)
|
|
36
|
+
const toolName: DiffRecord['toolName'] = r.toolName === 'write' || r.toolName === 'remote_ssh_write' ? r.toolName : 'edit'
|
|
36
37
|
const rawDecisions = r.decisions && typeof r.decisions === 'object' ? r.decisions as Record<string, unknown> : {}
|
|
37
38
|
const call = rawDecisions.call === 'accepted' || rawDecisions.call === 'rejected' ? rawDecisions.call : 'pending'
|
|
38
39
|
const rawPerHunk = Array.isArray(rawDecisions.perHunk) ? rawDecisions.perHunk : []
|