@openvulcan/dsh-vulcancode 0.4.0 → 0.4.4

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.
Files changed (39) hide show
  1. package/README.md +103 -38
  2. package/cordis.patch.yml +4 -0
  3. package/node_modules/@openvulcan/dsh-decision-guard/lib/client.js +133 -38
  4. package/node_modules/@openvulcan/dsh-decision-guard/package.json +2 -2
  5. package/node_modules/@openvulcan/dsh-vulcan-account/lib/client.js +55 -47
  6. package/node_modules/@openvulcan/dsh-vulcan-account/package.json +2 -2
  7. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/client.js +30 -30
  8. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/committed-fiber.d.ts +18 -0
  9. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/committed-fiber.js +62 -0
  10. package/node_modules/@openvulcan/dsh-vulcan-answers/package.json +1 -1
  11. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/client.js +14 -5
  12. package/node_modules/@openvulcan/dsh-vulcan-luaskills/package.json +1 -1
  13. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/markdown-memory-guide.md +1 -0
  14. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-curator/SKILL.md +1 -1
  15. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.d.ts +7 -0
  16. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.js +11 -0
  17. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.d.ts +1 -1
  18. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.js +1 -1
  19. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.js +22 -3
  20. package/node_modules/@openvulcan/dsh-vulcan-memory/package.json +1 -1
  21. package/node_modules/@openvulcan/dsh-vulcan-reasoning/LICENSE +21 -0
  22. package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/client.js +509 -0
  23. package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.d.ts +6 -0
  24. package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.js +8 -0
  25. package/node_modules/@openvulcan/dsh-vulcan-reasoning/package.json +42 -0
  26. package/node_modules/@openvulcan/dsh-vulcan-style/lib/client.js +67 -3
  27. package/node_modules/@openvulcan/dsh-vulcan-style/package.json +1 -1
  28. package/node_modules/@openvulcan/dsh-vulcan-voice-input/LICENSE +21 -0
  29. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/client.js +1567 -0
  30. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/cloud.d.ts +39 -0
  31. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/cloud.js +187 -0
  32. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/contract.d.ts +58 -0
  33. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/contract.js +1 -0
  34. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/index.d.ts +49 -0
  35. package/node_modules/@openvulcan/dsh-vulcan-voice-input/lib/index.js +204 -0
  36. package/node_modules/@openvulcan/dsh-vulcan-voice-input/package.json +46 -0
  37. package/node_modules/@openvulcan/openvulcan-account/package.json +1 -1
  38. package/node_modules/@openvulcan/vulcan-decision/package.json +2 -2
  39. package/package.json +14 -10
package/README.md CHANGED
@@ -10,62 +10,127 @@
10
10
  **把 VulcanCode 的体验带进 DeepSeek Harness(dsh)**
11
11
 
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
13
+ [![npm](https://img.shields.io/npm/v/@openvulcan/dsh-vulcancode)](https://www.npmjs.com/package/@openvulcan/dsh-vulcancode)
13
14
 
14
15
  **简体中文** | [English](README.en.md)
15
16
 
17
+ [特色功能](#特色功能) · [界面截图](#界面截图) · [架构与包结构](#架构与包结构) · [快速开始](#快速开始) · [开发](#开发)
18
+
16
19
  </div>
17
20
 
18
21
  ---
19
22
 
20
23
  ## 关于项目
21
24
 
22
- dsh-vulcancode 是 DeepSeek Harness(dsh)的一组社区插件,安装一个包即可得到 VulcanCode 的几项核心能力。
23
- 适配 dsh `0.2.1-rc.2` 及以上、`0.3.0` 以下:
24
-
25
- ```sh
26
- dsh plugin add @openvulcan/dsh-vulcancode
27
- ```
25
+ dsh-vulcancode 是 DeepSeek Harness(dsh)的社区插件套件。安装一个包,即可在 dsh 中使用 VulcanCode 的界面风格、交互式回答、自动审批、LuaSkills 和长期记忆,并为 pi-ai 模型配置思考级别。
28
26
 
29
27
  本项目与 DeepSeek 无关联,dsh 是 DeepSeek 的产品。
30
28
 
31
- | 组成 | 作用 |
32
- | --- | --- |
33
- | OpenVulcan 账号 | 登录一次,套件内各插件共用;凭据保存在 dsh 的凭据存储中。设置位于「设置 › OpenVulcan 账号」 |
34
- | 自动审批(Vulcan Decision) | 每次工具调用前由专用判定模型判断放行、需确认或拦下;未放行的可交复核 LLM,再询问你或拒绝 |
35
- | 交互式回答 | 模型可以用 VulcanCode 的 `ui-*` 组件作答,在对话中渲染;启用后默认开启,可按会话关闭 |
36
- | LuaSkills | 技能以工具形式提供给 AI,默认技能 codekit(代码导航);运行库约 190 MB,在「设置 › LuaSkills」中按需安装,装好即自动接入。侧栏的 LuaSkills 页面管理已装技能并连接 LuaSkills Hub |
37
- | Vulcan 界面风格 | VulcanCode 的主题、直角与配色 |
38
- | 长期记忆 | VulcanCode 的 Markdown 长期记忆:AI 用 `memory_outline`、`memory_search`、`memory_write` 维护用户画像、空间记忆与项目记忆,每次会话开始时注入一份快照。侧栏的「长期记忆」页面可编辑、调整注入预算,并导入导出 |
29
+ ## 特色功能
39
30
 
40
- > **我们承诺:云端命令判定模型永久免费提供。**(2026 年 10 月 27 日)
31
+ - **桌面工作台**采用 VulcanCode 配色、可独立开关的直角外观,以及加宽后能充分利用内容区的设置页面。
32
+ - **结果可交互**,模型可在对话中用图表、流程图和决策卡片呈现结果。卡片里的选择可以带回输入框继续讨论。
33
+ - **工具调用可审查**,Vulcan Decision 在执行前使用云端或本机模型判定,并支持配置复核和人工确认。
34
+ - **技能与经验可复用**,LuaSkills 把技能发布为 AI 可调用的工具,Markdown 长期记忆保留用户、空间和项目经验。
35
+ - **模型可单独配置思考级别**,pi-ai 模型的可选级别和接口传输值可按供应商、按模型设置。
36
+ - **语音输入**使用 OpenVulcan 账号所属站点的免费转写服务,把口述内容插入对话草稿。
41
37
 
42
- ## 使用
38
+ ## 界面截图
43
39
 
44
- 1. 安装后重启 dsh,打开「设置 › OpenVulcan 账号」,选择国际站或中国站登录。
45
- 2. 打开「设置 › 自动审批」,在权限选择器中选择“自动审查”,或把接入方式改为“独立开关”。不登录也可以改用本机
46
- 模型判定。
47
- 3. 需要 LuaSkills 时,打开「设置 › LuaSkills」点击安装运行库,进度显示在页面上,完成后技能工具自动出现。
48
- 4. 交互式回答、界面风格和长期记忆安装即生效,可在各自的设置或侧栏页面中调整。
40
+ ### 暗色主题 · 工具分类卡片
49
41
 
50
- ## 自动审批的边界
42
+ ![Vulcan 暗色主题下,交互式回答以卡片展示工具分类](docs/assets/dsh-vulcan-dark.png)
43
+
44
+ ### 指挥舱主题 · 记忆工具验证
45
+
46
+ ![Vulcan 指挥舱主题下,交互式回答以统计卡片、检查清单和表格展示记忆工具验证结果](docs/assets/dsh-vulcan-command.png)
47
+
48
+ ## 架构与包结构
49
+
50
+ dsh 通过套件的 `cordis.patch.yml` 装载八个插件。自动审批和语音输入通过 `openvulcanAccount` 获取云端服务所需的凭据。账号插件和判定引擎复用同一账号库。发布包随附工作区依赖,用户只需安装套件。
51
+
52
+ ### 套件入口
53
+
54
+ #### `@openvulcan/dsh-vulcancode`
55
+
56
+ 套件通过 `dsh.bundle.patch` 声明插件组。dsh 加载各子插件的 `apply(ctx)` 入口,账号插件则导出 Cordis 服务类。安装后可在插件管理页查看套件及其组件。源码在 [packages/dsh-vulcancode](packages/dsh-vulcancode)。
57
+
58
+ ### dsh 插件
59
+
60
+ #### `@openvulcan/dsh-vulcan-account`
61
+
62
+ 在「设置 › OpenVulcan 账号」选择国际站或中国站,浏览器授权后即可在 dsh 内查看账号状态或退出登录。插件将凭据保存到 dsh 的凭据存储,并通过 `openvulcanAccount` 向云端判定等集成提供账号服务。源码在 [packages/dsh-vulcan-account](packages/dsh-vulcan-account)。
63
+
64
+ #### `@openvulcan/dsh-decision-guard`
65
+
66
+ 在「设置 › 自动审批」选择与权限预设的接入方式,配置云端或本机判定、复核 LLM 和询问规则,并查看审批记录。判定模型给出放行、需确认或拦截三类结果。未放行的调用按配置进入复核、询问或拒绝。详见[自动审批的边界](#自动审批的边界)和[插件说明](packages/dsh-decision-guard/README.md)。
67
+
68
+ #### `@openvulcan/dsh-vulcan-answers`
69
+
70
+ 在对话中渲染模型输出的 `ui-*` 代码围栏,例如对比表、图表、流程图、时序图和决策卡片。决策选项可以插入输入框或发送。对话默认使用交互模式,输入框里的「回答」可以按会话切换为标准 Markdown,从下次请求起生效。源码在 [packages/dsh-vulcan-answers](packages/dsh-vulcan-answers)。
71
+
72
+ #### `@openvulcan/dsh-vulcan-luaskills`
73
+
74
+ 在「设置 › LuaSkills」按需安装约 190 MB 的运行库和默认代码导航技能 CodeKit。安装后引擎和工具自动接入,可以调整 VM 池、缓存、受管 Python / Node 和网络设置。侧栏「LuaSkills」可启用、停用、更新或卸载技能,也可从 LuaSkills Hub 搜索和安装新技能。源码在 [packages/dsh-vulcan-luaskills](packages/dsh-vulcan-luaskills)。
75
+
76
+ #### `@openvulcan/dsh-vulcan-memory`
77
+
78
+ 以 Markdown 保存用户画像、空间记忆和项目记忆。向模型提供 `memory_outline`、`memory_search`、`memory_write` 与 `memory-curator` 技能,并在会话开始时按预算注入快照。侧栏「长期记忆」支持编辑、调整注入预算和导入导出。当前可用的是 Markdown 模式。源码在 [packages/dsh-vulcan-memory](packages/dsh-vulcan-memory)。
79
+
80
+ #### `@openvulcan/dsh-vulcan-reasoning`
51
81
 
52
- - 它防的是 AI 执行命令时的**失误**,不防 AI **刻意绕过**(例如先写一个脚本再运行它)。
53
- - 任何错误都不会被当作放行;无人值守的会话从不询问你,复核未放行即拒绝。
54
- - 自动审批只降低事故概率,不保证拒绝一切风险;请同时使用沙箱、Git 与备份。
82
+ 在「设置 › 模型」展开已配置的 pi-ai 提供商卡片中的「自定义思考级别」。按模型选择跟随目录、关闭思考,或自定义对话中可选的级别及接口传输值。保存后在对话模型选择器中选用,传输值以对应模型接口支持的参数为准。源码在 [packages/dsh-vulcan-reasoning](packages/dsh-vulcan-reasoning)。
55
83
 
56
- ## 包结构
84
+ #### `@openvulcan/dsh-vulcan-style`
85
+
86
+ 在「设置 › 通用设置」的「Vulcan 界面风格」选择跟随系统、亮色、暗色、指挥舱或 dsh 自带主题。直角开关可与任意主题搭配,覆盖 Windows 桌面内容区等界面边角。设置布局为模型目录、内置插件和 LuaSkills 等页面留出更多可用宽度。源码在 [packages/dsh-vulcan-style](packages/dsh-vulcan-style)。
87
+
88
+ #### `@openvulcan/dsh-vulcan-voice-input`
89
+
90
+ 登录 OpenVulcan 后,将鼠标移到对话输入框的麦克风按钮,可选择所属站点发布的免费转写模型、优先语言、麦克风和录音快捷键。可在「设置 › OpenVulcan 语音输入」测试麦克风和查看服务状态。录音快捷键支持单独绑定右 Ctrl 等左右修饰键。按住麦克风按钮或在输入框内按住所设快捷键说话,松开后转写内容会回填当前草稿。连续录满 120 秒会自动停止并转写。源码在 [packages/dsh-vulcan-voice-input](packages/dsh-vulcan-voice-input)。
91
+
92
+ ### 共享库与命令行
93
+
94
+ #### `@openvulcan/vulcan-decision`
95
+
96
+ 为宿主提供 `allow`、`review` 和 `block` 判定,支持 OpenVulcan 云端端点和按需安装的本机运行包。随附的 `vulcan-decision` CLI 可用于探测云端、检查硬件、管理本机运行包和模型,以及对单次工具调用执行判定。详见[判定引擎与命令行说明](packages/vulcan-decision/README.md)。
97
+
98
+ #### `@openvulcan/openvulcan-account`
99
+
100
+ 封装设备授权、凭据刷新、账号资料查询和设备注销,并定义 `CredentialStore` 存储接口。dsh 插件接入宿主凭据存储,独立 CLI 可使用库内的文件存储实现。源码在 [packages/openvulcan-account](packages/openvulcan-account)。
101
+
102
+ ## 快速开始
103
+
104
+ 套件声明兼容 dsh `>=0.2.1-rc.2 <0.3.0`。
105
+
106
+ ### 桌面版
107
+
108
+ 在 dsh 侧栏打开「插件」,点击「添加插件」,在安装框中粘贴以下 npm 包名并安装。
109
+
110
+ ```text
111
+ @openvulcan/dsh-vulcancode
112
+ ```
113
+
114
+ ### 网页版
115
+
116
+ ```sh
117
+ dsh plugin --profile web add @openvulcan/dsh-vulcancode
118
+ ```
119
+
120
+ ### 使用
121
+
122
+ 1. 安装后重启 dsh。使用云端判定时,在「设置 › OpenVulcan 账号」完成浏览器授权。选用本机判定可以跳过账号登录。
123
+ 2. 打开「设置 › 自动审批」配置判定方式。在权限选择器中选「自动审查」可对当前会话启用,该预设使用完全权限。改用「独立开关」会为所有会话叠加判定,并保留已有权限预设的文件沙箱。
124
+ 3. 需要技能工具时,在「设置 › LuaSkills」安装运行库。进度显示在页面上,完成后技能工具自动接入。
125
+ 4. 界面风格、交互式回答和 Markdown 长期记忆默认启用。思考级别可以在配置好 pi-ai 提供商后按模型调整。
126
+
127
+ > **我们承诺云端命令判定模型永久免费提供。**(2026 年 10 月 27 日)
128
+
129
+ ## 自动审批的边界
57
130
 
58
- | 包 | 说明 |
59
- | --- | --- |
60
- | `@openvulcan/dsh-vulcancode` | 套件本体,dsh 安装的唯一入口,其余包都打包在内 |
61
- | `@openvulcan/dsh-vulcan-account` | OpenVulcan 账号插件,向其他插件提供 `openvulcanAccount` 服务 |
62
- | `@openvulcan/dsh-decision-guard` | 自动审批插件 |
63
- | `@openvulcan/dsh-vulcan-answers` | 交互式回答插件 |
64
- | `@openvulcan/dsh-vulcan-luaskills` | LuaSkills 插件:运行库安装、技能工具、VM 设置与 Hub |
65
- | `@openvulcan/dsh-vulcan-style` | 界面风格插件 |
66
- | `@openvulcan/dsh-vulcan-memory` | 长期记忆插件:记忆工具、会话快照注入、memory-curator 技能与记忆页面 |
67
- | `@openvulcan/vulcan-decision` | 判定引擎与命令行 `vulcan-decision` |
68
- | `@openvulcan/openvulcan-account` | OpenVulcan 账号库:登录、刷新与凭据存储接口 |
131
+ - 自动审批可以降低 AI 误执行命令的风险。AI 刻意绕过时,仍可能先写脚本再运行它。
132
+ - 任何错误都不会被当作放行。无人值守时,需要复核的调用只有在复核明确放行后才会执行。
133
+ - 请同时使用沙箱、Git 与备份。
69
134
 
70
135
  ## 开发
71
136
 
@@ -76,7 +141,7 @@ pnpm run test
76
141
  pnpm run pack:dsh # 生成 dist/openvulcan-dsh-vulcancode-<版本>.tgz
77
142
  ```
78
143
 
79
- 本地安装打包结果:`dsh plugin add ./dist/openvulcan-dsh-vulcancode-<版本>.tgz`。
144
+ 在网页版配置中安装本地包时,执行 `dsh plugin --profile web add ./dist/openvulcan-dsh-vulcancode-<版本>.tgz`。
80
145
 
81
146
  ## 致谢
82
147
 
package/cordis.patch.yml CHANGED
@@ -15,3 +15,7 @@
15
15
  name: '@openvulcan/dsh-vulcan-luaskills'
16
16
  - id: vulcan-memory
17
17
  name: '@openvulcan/dsh-vulcan-memory'
18
+ - id: vulcan-reasoning
19
+ name: '@openvulcan/dsh-vulcan-reasoning'
20
+ - id: vulcan-voice-input
21
+ name: '@openvulcan/dsh-vulcan-voice-input'
@@ -305,6 +305,10 @@ window.__ModuleLoader__.load({
305
305
  'conflict.failed': '停用失败:',
306
306
  'page.title': '自动审批',
307
307
  'page.lead': 'AI 每次调用工具前,先由 Vulcan Decision 判定模型判断:安全的直接放行,拿不准的交给复核 LLM 或由你确认,危险的拦截。任何错误都不会被当作放行。',
308
+ 'page.sections': '自动审批设置分区',
309
+ 'page.section.engine': '判定引擎',
310
+ 'page.section.policy': '复核与规则',
311
+ 'page.section.records': '判定记录',
308
312
  'loading': '读取中…',
309
313
  'saveFailed': '保存失败:{error}',
310
314
  'readOnly': '当前配置为只读,无法修改。',
@@ -319,6 +323,7 @@ window.__ModuleLoader__.load({
319
323
  'status.auto.standalone': '独立开关方式不使用该预设,所有会话都受判定',
320
324
  'status.decision.cloud': 'OpenVulcan 云端 · {model}',
321
325
  'status.decision.cloudNoModel': 'OpenVulcan 云端 · 没有可用的模型',
326
+ 'status.decision.cloudLoadFailed': 'OpenVulcan 云端 · 无法读取判定模型',
322
327
  'status.decision.signedOut': 'OpenVulcan 云端 · 未登录,受管控的调用会询问你',
323
328
  'status.decision.local': '本机 · {pack} 运行 {model}',
324
329
  'status.decision.localMissing': '本机 · 尚未选择运行包与模型,受管控的调用会询问你',
@@ -593,8 +598,6 @@ window.__ModuleLoader__.load({
593
598
  'records.enabled': '记录判定',
594
599
  'records.enabledHint': '保存判定模型或复核参与的每次决定,含原始命令与路径,只保存在这台电脑上。关闭后不再写入新记录,已有记录保留到你清除为止。',
595
600
  'records.entryHint': '查看判定模型的决定,标记判错的结果并导出为训练样本。',
596
- 'records.open': '查看记录',
597
- 'records.back': '返回自动审批',
598
601
  'records.paused': '记录判定已关闭,不会写入新记录。',
599
602
  'records.hint': '保留最近 {records} 条、最长 {days} 天的记录。记录包含原始命令与路径,只保存在这台电脑上。',
600
603
  'records.count': '{count} 条记录',
@@ -660,6 +663,10 @@ window.__ModuleLoader__.load({
660
663
  'conflict.failed': 'Could not disable: ',
661
664
  'page.title': 'Auto approval',
662
665
  'page.lead': 'Before the AI runs a tool, the Vulcan Decision model judges the call: safe calls run, uncertain ones go to the review LLM or to you, dangerous ones are blocked. No error ever counts as an allow.',
666
+ 'page.sections': 'Auto approval settings sections',
667
+ 'page.section.engine': 'Decision engine',
668
+ 'page.section.policy': 'Review and rules',
669
+ 'page.section.records': 'Decision records',
663
670
  'loading': 'Loading…',
664
671
  'saveFailed': 'Could not save: {error}',
665
672
  'readOnly': 'The configuration is read-only.',
@@ -674,6 +681,7 @@ window.__ModuleLoader__.load({
674
681
  'status.auto.standalone': 'The standalone switch does not use the preset; every session is judged',
675
682
  'status.decision.cloud': 'OpenVulcan cloud · {model}',
676
683
  'status.decision.cloudNoModel': 'OpenVulcan cloud · no model available',
684
+ 'status.decision.cloudLoadFailed': 'OpenVulcan cloud · could not load decision models',
677
685
  'status.decision.signedOut': 'OpenVulcan cloud · not signed in; governed calls ask you',
678
686
  'status.decision.local': 'This machine · {pack} runs {model}',
679
687
  'status.decision.localMissing': 'This machine · no runtime pack and model chosen; governed calls ask you',
@@ -948,8 +956,6 @@ window.__ModuleLoader__.load({
948
956
  'records.enabled': 'Record decisions',
949
957
  'records.enabledHint': 'Keep every decision the decision model or the review took part in, with the original commands and paths, on this computer only. Turning it off stops new records; existing ones stay until you clear them.',
950
958
  'records.entryHint': 'See the decision model\'s answers, mark wrong ones and export them as training samples.',
951
- 'records.open': 'View records',
952
- 'records.back': 'Back to auto approval',
953
959
  'records.paused': 'Recording is off, so no new records are written.',
954
960
  'records.hint': 'Keeps the latest {records} records for at most {days} days. Records hold the original commands and paths, on this computer only.',
955
961
  'records.count': '{count} records',
@@ -1072,26 +1078,45 @@ window.__ModuleLoader__.load({
1072
1078
  ctx.effect(() => ctx.locale.register(i18n_1.NAMESPACE, { zh: i18n_1.zh, en: i18n_1.en }), 'vulcan-decision-guard: dictionaries');
1073
1079
  ctx.effect(() => {
1074
1080
  let accountKey;
1081
+ // A reconnect can keep the same authorization ID while replacing the backend.
1082
+ // 重连可能更换宿主,但仍保留相同的授权标识。
1083
+ let refreshCloud = true;
1084
+ /**
1085
+ * Invalidate account-scoped data and read the new Host state after reconnect or plugin changes.
1086
+ * 重连或插件变更后清除账号相关数据,并读取新的宿主状态。
1087
+ */
1075
1088
  const refreshAll = () => {
1089
+ refreshCloud = true;
1090
+ services.panel.reset();
1091
+ services.cloudModels.reset();
1092
+ services.endpoints.reset();
1076
1093
  services.autoReview.refresh();
1077
1094
  services.autoListed.refresh();
1078
1095
  services.panel.refresh();
1079
1096
  };
1080
1097
  const stops = [
1081
- ctx.remote.$on('plugin-manager/changed', () => { services.autoReview.refresh(); }),
1098
+ ctx.remote.$on('plugin-manager/changed', refreshAll),
1082
1099
  ctx.remote.$on('permission-presets/catalog-changed', () => { services.autoListed.refresh(); }),
1083
1100
  ctx.on('connection/reset', refreshAll),
1084
- // Signing in and out happens in the account plugin; the models and endpoints belong to the account.
1101
+ // Load the model list when the first signed-in account arrives, without opening any UI.
1102
+ // 首次确认账号已登录时主动加载模型目录,无需打开界面。
1085
1103
  services.panel.subscribe(() => {
1086
1104
  const account = services.panel.getSnapshot()?.account;
1087
1105
  if (account === undefined)
1088
1106
  return;
1089
- const key = account.signedIn ? account.authorizationId ?? '' : null;
1090
- if (accountKey !== undefined && key !== accountKey) {
1107
+ const key = account.signedIn ? `${account.site ?? ''}:${account.authorizationId ?? ''}` : null;
1108
+ if (!refreshCloud && key === accountKey)
1109
+ return;
1110
+ const hadAccount = accountKey !== undefined;
1111
+ accountKey = key;
1112
+ refreshCloud = false;
1113
+ services.cloudModels.reset();
1114
+ services.endpoints.reset();
1115
+ if (account.signedIn) {
1091
1116
  services.cloudModels.refresh();
1092
- services.endpoints.refresh();
1117
+ if (hadAccount)
1118
+ services.endpoints.refresh();
1093
1119
  }
1094
- accountKey = key;
1095
1120
  }),
1096
1121
  ];
1097
1122
  refreshAll();
@@ -1112,6 +1137,12 @@ window.__ModuleLoader__.load({
1112
1137
  locale: i18n_1.NAMESPACE,
1113
1138
  inject: withServices,
1114
1139
  }, ({ t, services: injected }) => (0, jsx_runtime_1.jsx)(settings_1.SettingsPage, { services: injected, t: t })));
1140
+ // dsh 0.2.2 declares this optional slot. The injection waits on older hosts.
1141
+ // dsh 0.2.2 声明了此可选插槽,旧版宿主上的注入会保持等待。
1142
+ ctx.slots.inject('settings.section.icon', () => ctx.slots.register({
1143
+ name: 'settings.section.icon',
1144
+ id: shared_1.SETTINGS_SECTION,
1145
+ }, SettingsIcon));
1115
1146
  ctx.slots.inject('plugins.detail.badge', () => ctx.slots.register({
1116
1147
  name: 'plugins.detail.badge',
1117
1148
  id: 'vulcan-decision-guard-conflict',
@@ -1158,6 +1189,13 @@ window.__ModuleLoader__.load({
1158
1189
  },
1159
1190
  }, quick_1.QuickSetting));
1160
1191
  }
1192
+ /**
1193
+ * Render the approval shield at the square size requested by dsh's settings navigation.
1194
+ * 按 dsh 设置导航请求的方形尺寸渲染审批盾牌图标。
1195
+ */
1196
+ function SettingsIcon({ size }) {
1197
+ return ((0, jsx_runtime_1.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [(0, jsx_runtime_1.jsx)("path", { d: "M12 3 20 6v5c0 4.7-2.8 7.9-8 10-5.2-2.1-8-5.3-8-10V6l8-3Z" }), (0, jsx_runtime_1.jsx)("path", { d: "m9 12 2 2 4-4" })] }));
1198
+ }
1161
1199
 
1162
1200
  },
1163
1201
  "./local": function (module, exports, require) {
@@ -1262,8 +1300,8 @@ window.__ModuleLoader__.load({
1262
1300
  return null;
1263
1301
  const governed = value.mode === 'standalone' || permissions?.currentValue === shared_1.AUTO_PRESET;
1264
1302
  const unattended = view?.unattended === true;
1265
- const decision = (0, settings_1.decisionStatus)(t, value, live, models?.kind === 'ready' ? models.models : undefined);
1266
- const dot = !governed ? 'off' : decision.tone === 'ok' ? 'ok' : 'warn';
1303
+ const decision = (0, settings_1.decisionStatus)(t, value, live, models);
1304
+ const dot = !governed || decision.tone === 'outline' ? 'off' : decision.tone === 'ok' ? 'ok' : 'warn';
1267
1305
  return ((0, jsx_runtime_1.jsxs)("span", { className: "vdg", children: [(0, jsx_runtime_1.jsxs)("button", { ref: anchor, type: "button", className: "vdg-trigger", "aria-haspopup": "dialog", "aria-expanded": open, title: decision.text, onClick: () => {
1268
1306
  if (!open) {
1269
1307
  services.panel.refresh();
@@ -1356,7 +1394,7 @@ window.__ModuleLoader__.load({
1356
1394
  "./records": function (module, exports, require) {
1357
1395
  "use strict";
1358
1396
  Object.defineProperty(exports, "__esModule", { value: true });
1359
- exports.RecordsPage = RecordsPage;
1397
+ exports.RecordsPanel = RecordsPanel;
1360
1398
  const jsx_runtime_1 = require("react/jsx-runtime");
1361
1399
  /**
1362
1400
  * Settings › Auto approval › Decision log, after VulcanCode's records page: every decision the
@@ -1403,7 +1441,11 @@ window.__ModuleLoader__.load({
1403
1441
  query.set('before', before);
1404
1442
  return `records?${query.toString()}`;
1405
1443
  }
1406
- function RecordsPage({ t, recording, onBack }) {
1444
+ /**
1445
+ * Render decision history and its actions using the dictionary and current recording state.
1446
+ * 根据本地化词典和当前记录开关状态渲染判定历史及其操作。
1447
+ */
1448
+ function RecordsPanel({ t, recording }) {
1407
1449
  const [filters, setFilters] = (0, react_1.useState)({ label: undefined, workspace: undefined, markedOnly: false });
1408
1450
  const [page, setPage] = (0, react_1.useState)(undefined);
1409
1451
  const [loadError, setLoadError] = (0, react_1.useState)(undefined);
@@ -1467,7 +1509,7 @@ window.__ModuleLoader__.load({
1467
1509
  }, 'records.clearFailed');
1468
1510
  };
1469
1511
  const clearButton = ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn ghost sm", disabled: busy !== undefined, onClick: () => { setClearing(true); }, children: t('records.clear') }));
1470
- return ((0, jsx_runtime_1.jsxs)("div", { className: "vdg vdg-page", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-page-head", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-link", onClick: onBack, children: ["\u2190 ", t('records.back')] }), (0, jsx_runtime_1.jsx)("h2", { style: { marginTop: 8 }, children: t('records.title') }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-lead", children: t('records.entryHint') })] }), recording ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('records.paused') }), page === undefined
1512
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [recording ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('records.paused') }), page === undefined
1471
1513
  ? (loadError === undefined
1472
1514
  ? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') })
1473
1515
  : ((0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsx)("span", { children: (0, i18n_1.fill)(t('records.loadFailed'), { error: loadError }) }), clearButton] }) })))
@@ -1544,16 +1586,28 @@ window.__ModuleLoader__.load({
1544
1586
  safe: { tool: 'shell_command', args: { command: 'git status', shell: 'bash' } },
1545
1587
  risky: { tool: 'shell_command', args: { command: 'git push --force origin main', shell: 'bash' } },
1546
1588
  };
1547
- /** What the status section says about decisions, from the saved settings and this machine's state. */
1548
- function decisionStatus(t, value, live, models) {
1589
+ // The sections appear in the same order in navigation and in the page.
1590
+ // 分区在导航和正文中按同一顺序排列。
1591
+ const SETTINGS_VIEWS = ['engine', 'policy', 'records'];
1592
+ /**
1593
+ * Describe the configured backend using the latest account and model-list response.
1594
+ * 使用最新账号和模型目录响应描述已配置的判定后端。
1595
+ */
1596
+ function decisionStatus(t, value, live, list) {
1549
1597
  if (live == null)
1550
1598
  return { tone: 'outline', text: t('loading') };
1551
1599
  if (value.backend === 'cloud') {
1552
1600
  if (!live.account.signedIn)
1553
1601
  return { tone: 'warn', text: t('status.decision.signedOut') };
1554
- const model = value.cloudModel === '' ? models?.[0] : value.cloudModel;
1555
- if (model === undefined || (models !== undefined && !models.includes(model)))
1602
+ if (list === undefined)
1603
+ return { tone: 'outline', text: t('model.loading') };
1604
+ if (list.kind === 'failed')
1605
+ return { tone: 'warn', text: t('status.decision.cloudLoadFailed') };
1606
+ const model = value.cloudModel === '' ? list.models[0] : value.cloudModel;
1607
+ if (list.models.length === 0 || model === undefined)
1556
1608
  return { tone: 'warn', text: t('status.decision.cloudNoModel') };
1609
+ if (!list.models.includes(model))
1610
+ return { tone: 'warn', text: (0, i18n_1.fill)(t('model.disabled'), { model }) };
1557
1611
  return { tone: 'ok', text: (0, i18n_1.fill)(t('status.decision.cloud'), { model }) };
1558
1612
  }
1559
1613
  const pack = live.packs.find(item => item.id === value.localPack && item.installed !== null);
@@ -1566,7 +1620,7 @@ window.__ModuleLoader__.load({
1566
1620
  const heldByReview = (0, ui_1.useWatch)(services.autoReview);
1567
1621
  const listed = (0, ui_1.useWatch)(services.autoListed);
1568
1622
  const models = (0, ui_1.useWatch)(services.cloudModels);
1569
- const decision = decisionStatus(t, value, live, models?.kind === 'ready' ? models.models : undefined);
1623
+ const decision = decisionStatus(t, value, live, models);
1570
1624
  let auto;
1571
1625
  if (value.mode === 'standalone')
1572
1626
  auto = { tone: 'outline', text: 'status.auto.standalone', badge: 'tone.off' };
@@ -1576,7 +1630,7 @@ window.__ModuleLoader__.load({
1576
1630
  auto = { tone: 'ok', text: 'status.auto.provided', badge: 'tone.ok' };
1577
1631
  else
1578
1632
  auto = { tone: 'outline', text: 'status.auto.pending', badge: 'status.auto.pending' };
1579
- return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('status.title'), flush: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-rows", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.decision') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [decision.text, (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: decision.tone, children: t(decision.tone === 'ok' ? 'tone.ok' : 'tone.warn') })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.auto') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [t(auto.text), (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: auto.tone, children: t(auto.badge) })] })] })] }) }));
1633
+ return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('status.title'), flush: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-rows", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.decision') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [decision.text, decision.tone === 'outline' ? null : (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: decision.tone, children: t(decision.tone === 'ok' ? 'tone.ok' : 'tone.warn') })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.auto') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [t(auto.text), (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: auto.tone, children: t(auto.badge) })] })] })] }) }));
1580
1634
  }
1581
1635
  function OptionsSection({ t, value, disabled, save }) {
1582
1636
  const row = (field, prefix, values) => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t(`${prefix}.title`) }), (0, jsx_runtime_1.jsx)(ui_1.Segments, { label: t(`${prefix}.title`), value: value[field], options: values.map(item => ({ value: item, title: t(`${prefix}.${item}`) })), disabled: disabled, onChoose: (next) => { save({ [field]: next }); } })] }));
@@ -1631,8 +1685,12 @@ window.__ModuleLoader__.load({
1631
1685
  }, children: t('prompts.save') })] })] }));
1632
1686
  }
1633
1687
  /** The decision log switch and the way into the log. */
1634
- function RecordsSection({ t, value, disabled, save, onOpen }) {
1635
- return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('records.title'), lead: t('records.entryHint'), actions: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-btn sm", onClick: onOpen, children: [t('records.open'), " \u2192"] }), children: (0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.recordDecisions, disabled: disabled, onChange: (event) => { save({ recordDecisions: event.target.checked }); } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: t('records.enabled') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: t('records.enabledHint') })] })] }) }));
1688
+ /**
1689
+ * Render the recording preference using the dictionary, current settings, disabled state and save callback.
1690
+ * 使用文案、当前设置、禁用状态与保存回调渲染判定记录开关。
1691
+ */
1692
+ function RecordsSection({ t, value, disabled, save }) {
1693
+ return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('records.title'), lead: t('records.entryHint'), children: (0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.recordDecisions, disabled: disabled, onChange: (event) => { save({ recordDecisions: event.target.checked }); } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: t('records.enabled') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: t('records.enabledHint') })] })] }) }));
1636
1694
  }
1637
1695
  function AskSection({ t, value, disabled, save }) {
1638
1696
  const [text, setText] = (0, react_1.useState)(String(value.askTimeout));
@@ -1661,10 +1719,22 @@ window.__ModuleLoader__.load({
1661
1719
  ? null
1662
1720
  : ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: verdict.decision === 'allow' ? 'ok' : verdict.decision === 'review' ? 'warn' : 'bad', children: ['allow', 'review', 'block'].includes(verdict.decision) ? t(`decision.${verdict.decision}`) : verdict.decision }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: (0, i18n_1.fill)(t('try.result'), { decision: verdict.decision, ms: verdict.elapsed_ms, backend: verdict.backend, via: verdict.via }) })] })), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error === undefined ? undefined : (0, i18n_1.fill)(t('try.failed'), { error }) })] }));
1663
1721
  }
1722
+ /**
1723
+ * Render the engine, policy and records views using the guard services and localized dictionary.
1724
+ * 使用审批服务与本地化词典渲染判定引擎、复核规则与记录分区。
1725
+ */
1664
1726
  function SettingsPage({ services, t }) {
1665
1727
  const settings = (0, ui_1.useSettings)(services.form, t);
1666
1728
  const live = (0, ui_1.useWatch)(services.panel);
1667
- const [view, setView] = (0, react_1.useState)('settings');
1729
+ // The selected section controls visibility while keeping settings drafts mounted.
1730
+ // 所选分区控制显示状态,同时保留已挂载的设置草稿。
1731
+ const [view, setView] = (0, react_1.useState)('engine');
1732
+ // The root is the scroll target when the user changes sections from the sticky navigation.
1733
+ // 用户从吸附导航切换分区时,将设置页根节点作为滚动目标。
1734
+ const page = (0, react_1.useRef)(null);
1735
+ // Each navigation control points to its own panel within this settings instance.
1736
+ // 每个分区按钮指向当前设置实例中的对应面板。
1737
+ const viewId = (0, react_1.useId)();
1668
1738
  const active = live != null && (0, shared_1.running)(live.job);
1669
1739
  (0, ui_1.usePolling)(services.panel, active ? 1000 : undefined);
1670
1740
  const value = settings.value;
@@ -1675,25 +1745,33 @@ window.__ModuleLoader__.load({
1675
1745
  return (0, jsx_runtime_1.jsx)("div", { className: "vdg vdg-page", children: (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('unavailable') }) });
1676
1746
  }
1677
1747
  const { disabled, save, saveAsync } = settings;
1678
- if (view === 'records')
1679
- return (0, jsx_runtime_1.jsx)(records_1.RecordsPage, { t: t, recording: value.recordDecisions, onBack: () => { setView('settings'); } });
1680
1748
  const refresh = () => {
1681
1749
  services.panel.refresh();
1682
1750
  services.cloudModels.refresh();
1683
1751
  services.endpoints.refresh();
1684
1752
  };
1685
- return ((0, jsx_runtime_1.jsxs)("div", { className: "vdg vdg-page", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-page-head", children: [(0, jsx_runtime_1.jsx)("h2", { children: t('page.title') }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-lead", children: t('page.lead') })] }), settings.writable ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('readOnly') }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: settings.error }), (0, jsx_runtime_1.jsx)(StatusSection, { services: services, t: t, value: value, live: live }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('mode.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('mode.title'), value: value.mode, options: ['auto-preset', 'standalone'].map(item => ({ value: item, title: t(`mode.${item}`), hint: t(`mode.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ mode: next }); } }) }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('backend.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('backend.title'), value: value.backend, options: ['cloud', 'local'].map(item => ({ value: item, title: t(`backend.${item}`), hint: t(`backend.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ backend: next }); } }) }), live === undefined
1686
- ? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') })
1687
- : live === null
1688
- ? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: t('panel.unavailable') })
1689
- : value.backend === 'cloud'
1690
- ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.AccountSection, { services: services, t: t, account: live.account }), live.account.signedIn
1691
- ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.ModelSection, { services: services, t: t, saved: value.cloudModel, disabled: disabled, onChoose: (model) => {
1692
- // A lock belongs to the model it was chosen for.
1693
- void saveAsync({ cloudModel: model === '' ? undefined : model, cloudEndpoint: undefined }).then(() => { services.endpoints.refresh(); });
1694
- } }), (0, jsx_runtime_1.jsx)(cloud_1.EndpointsSection, { services: services, t: t, disabled: disabled, onLock: endpoint => (0, cloud_1.lockEndpoint)(saveAsync, value.cloudModel, endpoint, t('readOnly')) })] }))
1695
- : null] }))
1696
- : (0, jsx_runtime_1.jsx)(local_1.LocalSections, { t: t, live: live, value: value, disabled: disabled, save: save, refresh: refresh }), (0, jsx_runtime_1.jsx)(OptionsSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(ReviewSection, { services: services, t: t, value: value, disabled: disabled, save: save }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(RulesSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(PromptsSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), (0, jsx_runtime_1.jsx)(AskSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(RecordsSection, { t: t, value: value, disabled: disabled, save: save, onOpen: () => { setView('records'); } }), (0, jsx_runtime_1.jsx)(TrySection, { t: t })] }));
1753
+ /**
1754
+ * Activate the requested settings section and return to its start in the host's scroll pane.
1755
+ * 激活指定的设置分区,并在宿主滚动容器内回到分区开头。
1756
+ */
1757
+ const selectView = (next) => {
1758
+ if (view === next)
1759
+ return;
1760
+ setView(next);
1761
+ page.current?.scrollIntoView({ block: 'start' });
1762
+ };
1763
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: page, className: "vdg vdg-page", "data-vulcan-settings-width": "full", children: [(0, jsx_runtime_1.jsxs)("header", { className: "vdg-page-head", "data-vulcan-settings-header": true, children: [(0, jsx_runtime_1.jsx)("h2", { children: t('page.title') }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-lead", children: t('page.lead') })] }), (0, jsx_runtime_1.jsx)("div", { className: "vdg-settings-navigation", role: "group", "aria-label": t('page.sections'), children: SETTINGS_VIEWS.map(item => ((0, jsx_runtime_1.jsx)("button", { id: `${viewId}-${item}-button`, type: "button", "aria-pressed": view === item, "aria-controls": `${viewId}-${item}`, onClick: () => { selectView(item); }, children: t(`page.section.${item}`) }, item))) }), settings.writable ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('readOnly') }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: settings.error }), (0, jsx_runtime_1.jsxs)("section", { className: "vdg-settings-group", id: `${viewId}-engine`, "aria-labelledby": `${viewId}-engine-button`, hidden: view !== 'engine', children: [(0, jsx_runtime_1.jsx)(StatusSection, { services: services, t: t, value: value, live: live }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('mode.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('mode.title'), value: value.mode, options: ['auto-preset', 'standalone'].map(item => ({ value: item, title: t(`mode.${item}`), hint: t(`mode.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ mode: next }); } }) }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('backend.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('backend.title'), value: value.backend, options: ['cloud', 'local'].map(item => ({ value: item, title: t(`backend.${item}`), hint: t(`backend.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ backend: next }); } }) }), live === undefined
1764
+ ? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') })
1765
+ : live === null
1766
+ ? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: t('panel.unavailable') })
1767
+ : value.backend === 'cloud'
1768
+ ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.AccountSection, { services: services, t: t, account: live.account }), live.account.signedIn
1769
+ ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.ModelSection, { services: services, t: t, saved: value.cloudModel, disabled: disabled, onChoose: (model) => {
1770
+ // A lock belongs to the model it was chosen for.
1771
+ void saveAsync({ cloudModel: model === '' ? undefined : model, cloudEndpoint: undefined }).then(() => { services.endpoints.refresh(); });
1772
+ } }), (0, jsx_runtime_1.jsx)(cloud_1.EndpointsSection, { services: services, t: t, disabled: disabled, onLock: endpoint => (0, cloud_1.lockEndpoint)(saveAsync, value.cloudModel, endpoint, t('readOnly')) })] }))
1773
+ : null] }))
1774
+ : (0, jsx_runtime_1.jsx)(local_1.LocalSections, { t: t, live: live, value: value, disabled: disabled, save: save, refresh: refresh }), (0, jsx_runtime_1.jsx)(OptionsSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(TrySection, { t: t })] }), (0, jsx_runtime_1.jsxs)("section", { className: "vdg-settings-group", id: `${viewId}-policy`, "aria-labelledby": `${viewId}-policy-button`, hidden: view !== 'policy', children: [(0, jsx_runtime_1.jsx)(ReviewSection, { services: services, t: t, value: value, disabled: disabled, save: save }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(RulesSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(PromptsSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), (0, jsx_runtime_1.jsx)(AskSection, { t: t, value: value, disabled: disabled, save: save })] }), (0, jsx_runtime_1.jsx)("section", { className: "vdg-settings-group", id: `${viewId}-records`, "aria-labelledby": `${viewId}-records-button`, hidden: view !== 'records', children: view === 'records' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(RecordsSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(records_1.RecordsPanel, { t: t, recording: value.recordDecisions })] })) : null })] }));
1697
1775
  }
1698
1776
 
1699
1777
  },
@@ -1776,6 +1854,14 @@ window.__ModuleLoader__.load({
1776
1854
  this.#set(undefined);
1777
1855
  });
1778
1856
  }
1857
+ /**
1858
+ * Clear the previous Host value and discard reads still in flight, keeping subscribers.
1859
+ * 清除旧宿主值并丢弃未完成的读取,同时保留订阅。
1860
+ */
1861
+ reset() {
1862
+ ++this.#generation;
1863
+ this.#set(undefined);
1864
+ }
1779
1865
  /** Stop publishing answers that are still in flight. */
1780
1866
  close() {
1781
1867
  ++this.#generation;
@@ -1838,6 +1924,15 @@ window.__ModuleLoader__.load({
1838
1924
  .vdg *, .vdg *::before, .vdg *::after { box-sizing: border-box; }
1839
1925
  .vdg-page { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 4); counter-reset: vdg-section; padding-bottom: calc(var(--vk-u) * 2); }
1840
1926
  .vdg-page-head h2 { margin: 0; font-size: 16px; font-weight: 600; }
1927
+
1928
+ /* Keep the section switcher reachable inside the host's single scrolling pane. */
1929
+ /* 在宿主唯一的滚动正文内,让分区切换栏始终可触达。 */
1930
+ .vdg-settings-navigation { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--vk-line); background: var(--vk-panel); }
1931
+ .vdg-settings-navigation > button { min-height: 36px; padding: 6px 12px; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--vk-ink-2); font: inherit; cursor: pointer; }
1932
+ .vdg-settings-navigation > button:hover { background: var(--vk-hover); color: var(--vk-ink); }
1933
+ .vdg-settings-navigation > button[aria-pressed="true"] { border-bottom-color: var(--vk-ember); color: var(--vk-ink); font-weight: 600; }
1934
+ .vdg-settings-navigation > button:focus-visible { outline: 2px solid var(--vk-signal); outline-offset: -3px; }
1935
+ .vdg-settings-group:not([hidden]) { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 4); min-width: 0; }
1841
1936
  .vdg-lead { margin: calc(var(--vk-u) * 1) 0 0; color: var(--vk-ink-2); }
1842
1937
  .vdg-hint { margin: 0; color: var(--vk-ink-3); font-size: 12px; }
1843
1938
  .vdg-error { margin: 0; color: var(--vk-bad); font-size: 12px; overflow-wrap: anywhere; }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openvulcan/dsh-decision-guard",
3
- "version": "0.4.0",
3
+ "version": "0.4.4",
4
4
  "description": "Vulcan Decision permission guard for DeepSeek Harness: every tool call is judged before it runs.",
5
5
  "license": "MIT",
6
6
  "icon": "./assets/decision-guard-icon.png",
@@ -44,7 +44,7 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@openvulcan/vulcan-decision": "0.4.0"
47
+ "@openvulcan/vulcan-decision": "0.4.4"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@deepseek-ai/cordis": "^4.0.5",