dsh-vscode-mode 0.1.48 → 0.1.49

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.
@@ -295,5 +295,48 @@
295
295
  [data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }
296
296
  [data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }
297
297
 
298
+ /* ===== rules panel (sidebar, Codebuddy rules view) ===== */
299
+ [data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }
300
+ [data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
301
+ [data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }
302
+ [data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
303
+ [data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }
304
+ [data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }
305
+ [data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
306
+ [data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }
307
+ [data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }
308
+ [data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }
309
+ [data-edrv-view] .edrv-rules-row { position: relative; padding: 7px 100px 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
310
+ [data-edrv-view] .edrv-rules-row.off .edrv-rules-file, [data-edrv-view] .edrv-rules-row.off .edrv-rules-desc { opacity: .5; }
311
+ [data-edrv-view] .edrv-rules-main { display: flex; align-items: center; gap: 6px; min-width: 0; cursor: pointer; }
312
+ [data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
313
+ [data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
314
+ [data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }
315
+ [data-edrv-view] .edrv-rules-type-always { background: rgba(15,157,88,.15); color: #0b7c46; }
316
+ [data-edrv-view] .edrv-rules-type-auto { background: rgba(79,140,255,.16); color: #3367c9; }
317
+ [data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }
318
+ [data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }
319
+ [data-edrv-view] .edrv-rules-desc { margin-top: 3px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
320
+ [data-edrv-view] .edrv-rules-actions { position: absolute; top: 5px; right: 4px; display: flex; align-items: center; gap: 2px; }
321
+ [data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); }
322
+ [data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
323
+ [data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: #b9c4cc; cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }
324
+ [data-edrv-view] .edrv-rules-switch.on { background: #7c5cff; }
325
+ [data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; transition: left .15s; }
326
+ [data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }
327
+ [data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }
328
+ [data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }
329
+ [data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }
330
+ [data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }
331
+ [data-edrv-view] .edrv-rules-editor-file { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex: 0 0 auto; }
332
+ [data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }
333
+ [data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }
334
+ [data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }
335
+ [data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }
336
+ [data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }
337
+ [data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }
338
+ [data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 12px; cursor: pointer; }
339
+ [data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }
340
+
298
341
 
299
342
 
@@ -23,6 +23,7 @@ import { revealInExplorer as revealPathInExplorer } from '../fileReveal.js'
23
23
  import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
24
24
  import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
25
25
  import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
26
+ import { getSidebarMinWidth } from '../sidebarMin.js'
26
27
  import { createNavHistory } from '../navHistory.js'
27
28
  import { statusOfAdd } from '../addToConversation.js'
28
29
  import { CACHE_KEY } from '../paths.js'
@@ -67,7 +68,7 @@ export function EditorView(props) {
67
68
  const [fileIdx, setFileIdx] = React.useState(0) // 全局差异文件位置(x/x 文件 显示)
68
69
  const [tabMenu, setTabMenu] = React.useState(null) // Tab 右键菜单 { x,y,path }(编辑区右键走 Monaco 原生菜单,无此浮层)
69
70
  const [sidebarOn, setSidebarOn] = React.useState(layout !== 'side') // 侧边栏显隐(侧栏形态默认收起)
70
- const [sidebarW, setSidebarW] = React.useState(240) // 侧边栏宽度
71
+ const [sidebarW, setSidebarW] = React.useState(() => getSidebarMinWidth()) // 侧边栏宽度(初值 = 最小宽度,默认 300)
71
72
  const [activePanel, setActivePanel] = React.useState('explorer') // 激活面板 id
72
73
  const [focusRequest, setFocusRequest] = React.useState(0) // 外部差异聚焦请求版本
73
74
  const kbVersion = useKeybindingsVersion() // 快捷键配置版本(tooltip 文案随键位刷新)
@@ -508,6 +509,7 @@ export function EditorView(props) {
508
509
  }, [tabs, active, sessionId])
509
510
 
510
511
  // 侧边栏状态:恢复(显隐/宽度/激活面板);侧栏形态独立键(默认收起,不共享页签形态偏好)
512
+ // 宽度下限来自通用设置 sidebarMinWidth(默认 300),恢复值按其重夹
511
513
  const sidebarKey = CACHE_KEY.sidebar + (layout === 'side' ? 'side.' : '') + String(sessionId)
512
514
  React.useEffect(() => {
513
515
  if (!sessionId) return
@@ -516,12 +518,23 @@ export function EditorView(props) {
516
518
  if (raw) {
517
519
  const saved = JSON.parse(raw)
518
520
  if (typeof saved.on === 'boolean') setSidebarOn(saved.on)
519
- if (typeof saved.width === 'number') setSidebarW(Math.max(180, Math.min(560, saved.width)))
521
+ if (typeof saved.width === 'number') setSidebarW(Math.max(getSidebarMinWidth(), Math.min(560, saved.width)))
520
522
  if (typeof saved.panel === 'string') setActivePanel(saved.panel)
521
523
  }
522
524
  } catch (e) { /* 损坏忽略 */ }
523
525
  }, [sessionId, sidebarKey])
524
526
 
527
+ // 侧边栏最小宽度设置变更(edrv:sidebar-min-width)→ 更新 minW 并重夹当前宽度
528
+ const [sidebarMinW, setSidebarMinW] = React.useState(() => getSidebarMinWidth())
529
+ React.useEffect(() => {
530
+ const onMinChange = () => {
531
+ setSidebarMinW(getSidebarMinWidth())
532
+ setSidebarW((w) => Math.max(getSidebarMinWidth(), w))
533
+ }
534
+ window.addEventListener('edrv:sidebar-min-width', onMinChange)
535
+ return () => window.removeEventListener('edrv:sidebar-min-width', onMinChange)
536
+ }, [])
537
+
525
538
  // 侧边栏状态持久化
526
539
  React.useEffect(() => {
527
540
  if (!sessionId) return
@@ -1244,7 +1257,7 @@ export function EditorView(props) {
1244
1257
  'aria-label': '前进',
1245
1258
  onClick: navForward,
1246
1259
  }, '→'),
1247
- React.createElement('button', { className: 'edrv-chip-btn' + (sidebarOn ? ' edrv-chip-on' : ''), title: '切换侧边栏 (' + (chordOf('edrv.toggleSidebar') ?? 'Ctrl+B') + ')', onClick: () => setSidebarOn((v) => !v) }, '☰'),
1260
+ React.createElement('button', { className: 'edrv-chip-btn' + (sidebarOn ? ' edrv-chip-on' : ''), title: '切换面板区 (' + (chordOf('edrv.toggleSidebar') ?? 'Ctrl+B') + '),图标列常显', onClick: () => setSidebarOn((v) => !v) }, '☰'),
1248
1261
  React.createElement('button', { className: 'edrv-chip-btn', title: '刷新', onClick: reloadFile }, '⟳'),
1249
1262
  React.createElement(QuickOpen, { sessionId, onOpen: (p) => openFile(p, false) }))
1250
1263
 
@@ -1390,6 +1403,8 @@ export function EditorView(props) {
1390
1403
  const sidebarPanels = props.sidebarPanels
1391
1404
  const sidebarCtx = {
1392
1405
  sessionId,
1406
+ // 当前会话工作区(sessions 快照 byId[sessionId].cwd;规则面板项目 Tab 自动匹配)
1407
+ cwd: props.sessions?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd ?? null,
1393
1408
  openFile: (p) => openFile(p, false),
1394
1409
  openFileAt,
1395
1410
  activePath: active,
@@ -1432,15 +1447,18 @@ export function EditorView(props) {
1432
1447
  // 编辑器根节点按 composer 顶部边界动态限高,底部对话区域继续由 DSH 原生渲染。
1433
1448
  // 侧栏形态由面板容器给高(100%),不做 composer 几何同步。
1434
1449
  const editorRow = React.createElement('div', { className: 'edrv-editor-row' },
1435
- (sidebarOn && sidebarPanels
1450
+ (sidebarPanels
1436
1451
  ? React.createElement(SidebarView, {
1437
1452
  registry: sidebarPanels,
1438
1453
  ctx: sidebarCtx,
1439
1454
  activePanel,
1440
1455
  onActive: setActivePanel,
1456
+ visible: sidebarOn,
1457
+ onShow: () => setSidebarOn(true),
1441
1458
  width: sidebarW,
1442
1459
  onWidth: setSidebarW,
1443
1460
  onHide: () => setSidebarOn(false),
1461
+ minWidth: sidebarMinW,
1444
1462
  })
1445
1463
  : null),
1446
1464
  mainCol)
@@ -10,6 +10,7 @@ import type { MpcProject, MpcServer } from '../../shared/mcp.js'
10
10
  import '../styles/mcp.css'
11
11
  import { availableOpeners, AUTO_OPEN_TOOL } from '../fileOpeners.js'
12
12
  import { SettingsContext } from '../settingsContext.js'
13
+ import { normalizeSidebarMinWidth, SIDEBAR_MIN_DEFAULT } from '../sidebarMin.js'
13
14
  import { KeybindingsPanel } from './KeybindingsPanel.js'
14
15
  import { LspSettings } from './LspSettings.js'
15
16
  import { PerfSettings } from './PerfSettings.js'
@@ -122,7 +123,7 @@ function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove }) {
122
123
  return React.createElement('section', { className: 'vsm-project' }, head, body)
123
124
  }
124
125
 
125
- /** 通用设置:选择当前对话文件链接的打开器 + 开发形态开关。 */
126
+ /** 通用设置:选择当前对话文件链接的打开器 + 开发形态开关 + 编辑器布局(侧边栏最小宽度)。 */
126
127
  function GeneralSettings({ registry }) {
127
128
  const [tool, setTool] = React.useState(AUTO_OPEN_TOOL)
128
129
  const [busy, setBusy] = React.useState(false)
@@ -130,6 +131,8 @@ function GeneralSettings({ registry }) {
130
131
  const [devForm, setDevForm] = React.useState(null)
131
132
  const [devBusy, setDevBusy] = React.useState(false)
132
133
  const [devMessage, setDevMessage] = React.useState('')
134
+ const [minW, setMinW] = React.useState(SIDEBAR_MIN_DEFAULT)
135
+ const [minDraft, setMinDraft] = React.useState(null) // 输入草稿(null=跟随已保存值;blur/Enter 提交)
133
136
  const settings = React.useContext(SettingsContext)
134
137
  const snapshot = settings?.getSnapshot?.()
135
138
  const loading = !snapshot || snapshot.status === 'loading'
@@ -137,8 +140,10 @@ function GeneralSettings({ registry }) {
137
140
  const notReady = snapshot?.status !== 'ready'
138
141
  React.useEffect(() => {
139
142
  const onChange = () => {
140
- const next = settings?.getSnapshot?.().value?.fileOpenTool
143
+ const snap = settings?.getSnapshot?.()
144
+ const next = snap?.value?.fileOpenTool
141
145
  if (typeof next === 'string') setTool(next)
146
+ setMinW(normalizeSidebarMinWidth(snap?.value?.sidebarMinWidth))
142
147
  }
143
148
  onChange()
144
149
  return settings?.subscribe?.(onChange)
@@ -153,6 +158,19 @@ function GeneralSettings({ registry }) {
153
158
  if (!settings?.set) { setError('设置服务不可用'); setBusy(false); return }
154
159
  settings.set('fileOpenTool', value).catch((e) => setError(String(e))).finally(() => setBusy(false))
155
160
  }
161
+ /** 提交侧边栏最小宽度(归一化夹取 180–560 后持久化)。 */
162
+ const saveMinW = (raw) => {
163
+ const next = normalizeSidebarMinWidth(raw)
164
+ setMinW(next); setBusy(true); setError('')
165
+ if (!settings?.set) { setError('设置服务不可用'); setBusy(false); return }
166
+ settings.set('sidebarMinWidth', next).catch((e) => setError(String(e))).finally(() => setBusy(false))
167
+ }
168
+ /** 结束输入(blur/Enter)时提交草稿。 */
169
+ const commitMinW = () => {
170
+ if (minDraft === null) return
171
+ setMinDraft(null)
172
+ saveMinW(minDraft)
173
+ }
156
174
  const closeDevForm = () => {
157
175
  if (!window.confirm('关闭开发形态:插件将切换为正式版安装(版本依赖 + 删除工作区链接),pnpm 装配后需重启 DSH 生效。确认关闭?')) return
158
176
  setDevBusy(true)
@@ -184,6 +202,23 @@ function GeneralSettings({ registry }) {
184
202
  devFormRow,
185
203
  ),
186
204
  ),
205
+ React.createElement('section', { className: 'vsm-panel' },
206
+ React.createElement('h3', { className: 'vsm-panel-title' }, '编辑器'),
207
+ React.createElement('div', { className: 'vsm-panel-body' },
208
+ React.createElement('label', { className: 'vsm-general-row' },
209
+ React.createElement('span', null, '侧边栏最小宽度'),
210
+ React.createElement('input', {
211
+ type: 'number', min: 180, max: 560, step: 10,
212
+ value: minDraft ?? minW,
213
+ disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
214
+ title: '180–560 px;拖拽侧边栏低于该宽度时自动隐藏',
215
+ onChange: (event) => setMinDraft(event.target.value),
216
+ onBlur: commitMinW,
217
+ onKeyDown: (event) => { if (event.key === 'Enter') commitMinW() },
218
+ }),
219
+ React.createElement('small', null, '180–560 px;拖拽低于该宽度自动隐藏')),
220
+ ),
221
+ ),
187
222
  )
188
223
  }
189
224
 
@@ -44,5 +44,6 @@ export function SideEditorTab(props) {
44
44
  sidebarPanels: props?.sidebarPanels,
45
45
  outlineSources: props?.outlineSources,
46
46
  fileMenuItems: props?.fileMenuItems,
47
+ sessions: props?.sessions,
47
48
  })
48
49
  }
@@ -28,6 +28,7 @@ export interface SettingsDeps {
28
28
  object: (shape: Record<string, unknown>) => { default: (value: Record<string, unknown>) => unknown }
29
29
  string: () => { default: (value: string) => unknown }
30
30
  boolean: () => { default: (value: boolean) => unknown }
31
+ number: () => { default: (value: number) => unknown }
31
32
  }
32
33
  }
33
34
 
@@ -208,6 +209,7 @@ export async function installOpenSettingsSection(
208
209
  const schema = deps.z.object({
209
210
  fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
210
211
  keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
212
+ sidebarMinWidth: deps.z.number().default(300),
211
213
  })
212
214
  const strategy = await runSettingsInstall(ctx, ns, schema, entry, {
213
215
  setSource: (source) => hooks.setSource(source as () => FileOpenSettings),
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import { createLspManager } from './lsp/manager.js'
22
22
  import { createLspRpc } from './lsp/rpc.js'
23
23
  import { installLspSettingsSection } from './lsp/settings.js'
24
24
  import { disposeAllServers } from './lsp/transport.js'
25
+ import { installRulesSection } from './rules.js'
25
26
  import type { RpcHandlerMap } from './shared/rpc.js'
26
27
  import type { Registry } from './registry.js'
27
28
  import type { Ctx } from './store.js'
@@ -45,6 +46,9 @@ export function apply(ctx: Ctx, config?: unknown): void {
45
46
  const warnings: string[] = []
46
47
  setupOpenSettings(ctx, config, () => {})
47
48
  void installLspSettingsSection(ctx, {})
49
+ /** 规则注入 section(~/.dsh/rules 与 <工作区>/.dsh/rules;旧版 DSH 无 systemPrompt 时静默降级)。 */
50
+ const rulesInstalled = installRulesSection(ctx)
51
+ if (!rulesInstalled) ctx.logger?.warn?.('[' + name + '] 未检测到 systemPrompt 服务,规则仅可管理不注入')
48
52
  /** LSP RPC 与会话清理(一次性创建,tracker 状态跨请求保留)。 */
49
53
  const lspRpc = createLspRpc({ ctx, pluginConfig: config, manager: lspManager })
50
54
  const lspHandlers: Partial<RpcHandlerMap> = lspRpc.handlers
@@ -77,7 +81,7 @@ export function apply(ctx: Ctx, config?: unknown): void {
77
81
  disposeAllServers()
78
82
  })
79
83
 
80
- ctx.logger?.info?.('[' + name + '] 编辑差异审查已装配(/edrv/rpc 路由就绪,项目 MCP 隔离已启用,语言服务器 LSP 已接入)')
84
+ ctx.logger?.info?.('[' + name + '] 编辑差异审查已装配(/edrv/rpc 路由就绪,项目 MCP 隔离已启用,语言服务器 LSP 已接入,规则注入' + (rulesInstalled ? '已接入' : '未接入') + ')')
81
85
  }
82
86
 
83
87
  /**
package/src/rpc.ts CHANGED
@@ -28,6 +28,7 @@ import { newSearcher } from './search/orchestrator.js'
28
28
  import type { ContentSearcher } from './search/content.js'
29
29
  import { newContentSearcher } from './search/content.js'
30
30
  import { restoreFile, revertCall, revertHunk } from './revert.js'
31
+ import { rulesList, rulesRead, rulesRemove, rulesSave, rulesToggle } from './rules.js'
31
32
  import { listMcp, refreshMcp, removeMcp, saveMcp, toggleMcp } from './mcp.js'
32
33
  import { listProjects, projectRefresh, projectRemove, projectSave, projectToggle } from './mcpProject.js'
33
34
  import { normalizeFileOpenTool, FILE_OPEN_DEFAULT, FILE_OPEN_SETTINGS_NS } from './fileOpenSettings.js'
@@ -661,6 +662,42 @@ export function buildHandlers(
661
662
  return { ok: false, error: '撤销压缩配置失败:' + String(error) }
662
663
  }
663
664
  },
665
+ 'rules.list': async () => {
666
+ try {
667
+ return { ok: true, ...(await rulesList(ctx)) }
668
+ } catch (error) {
669
+ return { ok: false, error: '读取规则失败:' + String(error) }
670
+ }
671
+ },
672
+ 'rules.read': async (args) => {
673
+ try {
674
+ return { ok: true, content: await rulesRead(ctx, args) }
675
+ } catch (error) {
676
+ return { ok: false, error: '读取规则失败:' + String(error) }
677
+ }
678
+ },
679
+ 'rules.save': async (args) => {
680
+ try {
681
+ return { ok: true, rule: await rulesSave(ctx, args) }
682
+ } catch (error) {
683
+ return { ok: false, error: '保存规则失败:' + String(error) }
684
+ }
685
+ },
686
+ 'rules.remove': async (args) => {
687
+ try {
688
+ await rulesRemove(ctx, args)
689
+ return { ok: true }
690
+ } catch (error) {
691
+ return { ok: false, error: '删除规则失败:' + String(error) }
692
+ }
693
+ },
694
+ 'rules.toggle': async (args) => {
695
+ try {
696
+ return { ok: true, rule: await rulesToggle(ctx, args, args.enabled === true) }
697
+ } catch (error) {
698
+ return { ok: false, error: '切换规则失败:' + String(error) }
699
+ }
700
+ },
664
701
  }
665
702
  }
666
703