dsh-yolo-mode 0.5.2-beta.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -2,17 +2,78 @@
2
2
 
3
3
  本项目遵循 [Semantic Versioning](https://semver.org/)。
4
4
 
5
- ## [0.5.2-beta.0] - 2026-09-24
5
+ ## [0.6.0] - 2026-09-27
6
+
7
+ ### 变更:全部配置移入插件自身 UI 面板(设置页退役)
8
+
9
+ - **移除 `settings.section` 注册**(原 id `yolo-mode`,order 25):不再向 DSH 设置面板
10
+ 注册任何页面,全部配置项改由插件自身的 UI 面板承载 —— 点击输入框左侧的
11
+ `YOLO <预设>` 胶囊打开(slot `shell.overlay`)。
12
+ - 面板新增 **配置 / 状态与日志** 两个标签,默认打开「配置」:
13
+ - 「配置」渲染原设置页的完整表单(预设 / 模式 / 裁判供应商与模型 / 超时 / 最大令牌 /
14
+ 并发 / 系统提示词 / 层级表),保存仍走 `settingsMutate` 路径操作 + 乐观锁;
15
+ - 「状态与日志」保留统计卡、裁决表分页、「打开日志」与「刷新」。
16
+ - `src/client/ui/SettingsSection.js` → `src/client/ui/ConfigForm.js`(导出名
17
+ `ConfigForm`);组件内部逻辑不变,仅宿主从设置页改为面板。
18
+ - 文案 `nav` 退役,新增 `tabConfig` / `tabStatus`(中英双语)。
19
+
20
+ ### 修复:依赖解析与构建可复现
21
+
22
+ - `peerDependencies` 移除两个**已停更**的官方包:`@deepseek-ai/dsh-host-apiproxy`
23
+ 与 `@deepseek-ai/dsh-client-runtime`(npm 上分别止于 `0.1.1-rc.2`)。二者此前仅为
24
+ `import type` 引用(`verbatimModuleSyntax` 下会被擦除),却会让全新克隆的 `npm ci`
25
+ 因无法解析已停更 peer 而直接失败。
26
+ - `devDependencies` 的 `@deepseek-ai/dsh-llm` / `dsh-timeout` / `dsh-settings` 由
27
+ `^0.1.7-rc.1` 收紧为精确 `0.1.7-rc.2`:DSH 预发布包内部使用**精确 peer**,
28
+ rc.1 与 rc.2 混装会触发 `ERESOLVE` 冲突。现在可 `npm ci && npm test`(无需
29
+ `--legacy-peer-deps`)。
30
+ - `scripts/build-client.mjs` 移除硬编码开发机回退路径
31
+ (`E:/MyProjectCollection/Plugins/dsh-subagents-options/package.json`),改为在
32
+ 缺少 `rolldown` 时抛出可操作的错误提示。
33
+ - `package-lock.json` 重新生成(`resolved` 全部指向 `registry.npmjs.org`)。
34
+
35
+ ## [0.5.3] - 2026-09-25
36
+
37
+ ### 兼容:适配 DSH 0.1.7-rc.2
38
+
39
+ - 在本机 **0.1.7-rc.2** 上真实装载运行通过:全部入口激活、零错误、无 pending
40
+ (`did not activate` 告警不出现);设置读写(`settingsView` / `settingsMutate`
41
+ 乐观锁)实测往返成功。`dsh.compatibility.dshReleases` 新增
42
+ `"0.1.7-rc.2": "compatible"`;README 徽章与兼容性说明同步。
43
+ - 0.1.7-rc.1 → rc.2 的官方 `@deepseek-ai/dsh-*` 公开 API **无移除、无改名**
44
+ (`dsh-settings` / `dsh-client-connection` / `dsh-client-ui-slots` 逐字节相同,
45
+ 其余为增量新增),本插件无源码适配需求。
46
+
47
+ ### 修复:设置页「供应商」下拉为空
48
+
49
+ 设置页 `judgeProvider` 下拉始终为空。根因:0.1.7 起客户端 `connection.api.llm`
50
+ 已不存在,store 构造时 `this.llm` 恒为 `null`,`_fetchLlmDirectory()` 直接返回空数组。
51
+ 本次改为经客户端 Remote 服务读取:
52
+
53
+ - **供应商**:`ctx.remote.llm.listProviders()`(已注册路由)与
54
+ `listConfigurableProviders()`(已声明可配置)经 `joinProviderDirectory` 合并为与官方
55
+ settings-models 页一致的 `{provider, displayName}` 行。
56
+ - **模型**:`ctx.remote.session.modelCatalog()` 的 `groups`(按 provider 分组),
57
+ 与官方同一数据源。
58
+ - **inject**:`package.json` 的 `dsh.client.inject` 与客户端 `inject` 数组新增
59
+ `"remote.llm"`。
60
+ - **降级保留**:无 `remote.llm` 命名空间时回退旧 `connection.api.llm`;目录读取失败
61
+ 不致命,仍回退自由文本输入(既有设计不变)。
6
62
 
7
- ### Beta(DSH 0.1.7-rc.1 适配,测试版)
63
+ ### 测试
64
+
65
+ - 新增 `store-logic.joinProviderDirectory` 与 4 项 `YoloStore` `remote.llm` /
66
+ `remote.session` 单测(目录填充、失败非致命、legacy 回退、remote 优先);
67
+ `npm test` 137 项全绿。
68
+ - 本机无浏览器,客户端 UI 未在浏览器中实际渲染验证;已核对 `npm pack` 产物经
69
+ 0.1.7-rc.2 profile 装载后**被服务的**客户端 bundle 含 `remote.llm.listProviders`
70
+ / `listConfigurableProviders` / `session.modelCatalog`,且宿主 `llm/listProviders`、
71
+ `session/modelCatalog` 实测返回非空目录(供应商下拉将填充 5 项)。
72
+
73
+ ### Beta 说明
8
74
 
9
- - 本分支(`dev-0.1.7.rc1`,尚未合并 `main`)相对 `main` 的适配改动:设置子系统按
10
- 0.1.7 的 `SettingsForms` 新模型重写(`lib/settings.js` / `lib/index.js` /
11
- `lib/remote.js`),`@deepseek-ai/dsh-llm`、`dsh-timeout`、`dsh-settings`、
12
- `dsh-client-connection`、`dsh-client-ui-slots`、`dsh-client-locale` 的 peer 范围升至
13
- `^0.1.7-rc.1`,`dsh.compatibility.dshReleases` 新增 `"0.1.7-rc.1": "compatible"`,
14
- README 徽章与兼容性说明同步。详见下方 `0.5.2` 条目。
15
- - 发布为 npm 测试版(`--tag beta`);`latest` 保持不变。正式版 `0.5.2` 待 `main` 合并后发布。
75
+ - 测试版 `0.5.2-beta.0`(`--tag beta`)的内容——设置子系统迁移至 DSH 0.1.7 的
76
+ `SettingsForms`——随本版一并正式发布,详见下方 `0.5.2` 条目。
16
77
 
17
78
  ## [0.5.2] - 2026-09-24
18
79
 
package/README.md CHANGED
@@ -6,10 +6,12 @@
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/dsh-yolo-mode)](https://www.npmjs.com/package/dsh-yolo-mode)
8
8
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
- [![DeepSeek Harness](https://img.shields.io/badge/DSH%200.1.5--rc.1%20%2F%20rc.2%20%2F%200.1.6--alpha.2%20%2F%200.1.7--rc.1-blue)](https://github.com/deepseek-ai/deepseek-harness)
9
+ [![DeepSeek Harness](https://img.shields.io/badge/DSH%200.1.5--rc.1%20%2F%20rc.2%20%2F%200.1.6--alpha.2%20%2F%200.1.7--rc.1%20%2F%20rc.2-blue)](https://github.com/deepseek-ai/deepseek-harness)
10
10
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
11
11
 
12
- > **兼容性**:v0.5.2 支持 DSH **0.1.5-rc.1+**,已在 **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1** 实测(真实-Cordis 探针通过;设置子系统已迁移至 0.1.7 的 `SettingsForms`——插件 Config schema + volatile 热更 + 自建设置页)。旧版 DSH(0.1.0-rc.6 / rc.8)请使用最后兼容的 npm 版本 **0.4.1**。
12
+ > **维护状态**:官方已在 DSH 中提供实验性 Auto review 能力(`@deepseek-ai/dsh-experimental-auto-review`,LLM 逐工具授权审查,在 `ask` 审批策略下替代人工审批),与本插件的「LLM 裁判自动审批」功能重叠。因此本插件**可能在未来停止维护**;如需官方原生的自动审批,可评估该实验性功能。当前版本仍会跟进 DSH 兼容(最近实测 **0.1.7-rc.2**)。
13
+
14
+ > **兼容性**:v0.5.3 支持 DSH **0.1.5-rc.1+**,已在 **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2** 实测(真实-Cordis 探针通过;设置子系统已迁移至 0.1.7 的 `SettingsForms`——插件 Config schema + volatile 热更 + 自建设置页;0.1.7-rc.2 上 provider/model 目录改由 client `remote.llm` + `remote.session` 读取)。旧版 DSH(0.1.0-rc.6 / rc.8)请使用最后兼容的 npm 版本 **0.4.1**。
13
15
 
14
16
  ---
15
17
 
@@ -15,7 +15,8 @@ window.__ModuleLoader__.load({
15
15
  const NS = "settings.yoloMode";
16
16
  /** English dictionary. */
17
17
  const en = {
18
- nav: "YOLO Approval",
18
+ tabConfig: "Configuration",
19
+ tabStatus: "Status & log",
19
20
  chip: "YOLO status",
20
21
  sectionIntro: "Configure how YOLO mode auto-judges sandbox escalation approval requests.",
21
22
  loadError: "Could not load YOLO mode settings.",
@@ -73,7 +74,8 @@ window.__ModuleLoader__.load({
73
74
  };
74
75
  /** Chinese dictionary. */
75
76
  const zh = {
76
- nav: "YOLO 审批",
77
+ tabConfig: "配置",
78
+ tabStatus: "状态与日志",
77
79
  chip: "YOLO 状态",
78
80
  sectionIntro: "配置 YOLO 模式如何自动裁决沙箱升权审批申请。",
79
81
  loadError: "无法加载 YOLO 模式设置。",
@@ -234,6 +236,42 @@ window.__ModuleLoader__.load({
234
236
  if (code === "settings-rejected" || code === "schema-validation") return "rejected";
235
237
  return "fatal";
236
238
  }
239
+ /**
240
+ * Join the live provider routes (`remote.llm.listProviders`, rows `{id, name}`)
241
+ * with the declared configurable providers
242
+ * (`remote.llm.listConfigurableProviders`, rows `{provider, displayName, …}`)
243
+ * into one directory of `{ provider, displayName }` rows the select consumes.
244
+ * Declared entries lead (so a configured-but-not-yet-registered provider still
245
+ * appears), then any registered route the declaration does not name. Mirrors
246
+ * the official settings-models `joinProviderDirectory`.
247
+ */
248
+ function joinProviderDirectory(registered, declared) {
249
+ const routes = Array.isArray(registered) ? registered : [];
250
+ const directory = Array.isArray(declared) ? declared : [];
251
+ const rows = [];
252
+ const named = /* @__PURE__ */ new Set();
253
+ for (const entry of directory) {
254
+ if (entry === null || typeof entry !== "object") continue;
255
+ if (typeof entry.provider !== "string" || entry.provider === "") continue;
256
+ named.add(entry.provider);
257
+ rows.push({
258
+ provider: entry.provider,
259
+ displayName: typeof entry.displayName === "string" && entry.displayName !== "" ? entry.displayName : void 0,
260
+ active: routes.some((route) => route && route.id === entry.provider)
261
+ });
262
+ }
263
+ for (const route of routes) {
264
+ if (route === null || typeof route !== "object") continue;
265
+ if (typeof route.id !== "string" || route.id === "") continue;
266
+ if (named.has(route.id)) continue;
267
+ rows.push({
268
+ provider: route.id,
269
+ displayName: typeof route.name === "string" && route.name !== "" ? route.name : void 0,
270
+ active: true
271
+ });
272
+ }
273
+ return rows;
274
+ }
237
275
  //#endregion
238
276
  //#region src/client/store.js
239
277
  /**
@@ -266,9 +304,9 @@ window.__ModuleLoader__.load({
266
304
  writable: true,
267
305
  error: void 0,
268
306
  open: false,
269
- /** Configurable provider directory (llm.providers). */
307
+ /** Provider directory: remote.llm joined rows, or legacy llm.providers. */
270
308
  providers: [],
271
- /** Model catalog groups (llm.models). */
309
+ /** Model catalog groups: remote.session.modelCatalog, or legacy llm.models. */
272
310
  models: []
273
311
  };
274
312
  }
@@ -279,14 +317,20 @@ window.__ModuleLoader__.load({
279
317
  * @param {{ call: (channel: string, endpoint: string, payload: any) => Promise<any> }} options.rpc
280
318
  * rpc.call(channel, endpoint, payload) resolves to { ok: true, value } |
281
319
  * { ok: false, error }.
282
- * @param {object|null} [options.llm] - optional `connection.api.llm` face
320
+ * @param {object|null} [options.llm] - optional legacy `connection.api.llm` face
283
321
  * ({ providers(), models() } returning { result: { ok, value } }); when null
284
322
  * the directory stays empty and the judge fields fall back to free text.
323
+ * @param {object|null} [options.remote] - optional client Remote service; when
324
+ * it carries an `llm` namespace the directory comes from
325
+ * `llm.listProviders()` + `llm.listConfigurableProviders()` (0.1.7+) and the
326
+ * model catalog from `session.modelCatalog()`. Falls back to `llm` when the
327
+ * Remote namespace is absent.
285
328
  */
286
329
  var YoloStore = class {
287
- constructor({ rpc, llm }) {
330
+ constructor({ rpc, llm, remote }) {
288
331
  this.rpc = rpc;
289
332
  this.llm = llm == null ? null : llm;
333
+ this.remote = remote == null ? null : remote;
290
334
  this._state = initialYoloState();
291
335
  this._listeners = /* @__PURE__ */ new Set();
292
336
  this._generation = 0;
@@ -331,12 +375,54 @@ window.__ModuleLoader__.load({
331
375
  if (error !== null && typeof error === "object" && typeof error.code === "string") return error.code;
332
376
  }
333
377
  /**
334
- * Fetch the optional LLM directory (provider list + model catalog) when the
335
- * store was constructed with an llm face. Directory failures are NOT fatal:
336
- * they leave the snapshot's providers/models empty without flipping status
337
- * to 'error' — only the settings/status bridge calls can do that.
378
+ * Fetch the optional LLM directory (provider list + model catalog).
379
+ *
380
+ * 0.1.7+ exposes it through the client Remote service: the provider directory
381
+ * is the join of `remote.llm.listProviders()` (registered routes) and
382
+ * `remote.llm.listConfigurableProviders()` (declared providers), and the model
383
+ * catalog is `remote.session.modelCatalog().value.groups` — the same sources
384
+ * the official settings-models page reads. Older hosts without a Remote `llm`
385
+ * namespace fall back to the legacy `connection.api.llm` face.
386
+ *
387
+ * Directory failures are NOT fatal: they leave the snapshot's providers/models
388
+ * empty without flipping status to 'error' — only the settings/status bridge
389
+ * calls can do that.
338
390
  */
339
391
  async _fetchLlmDirectory() {
392
+ const fromRemote = await this._fetchRemoteDirectory();
393
+ if (fromRemote !== void 0) return fromRemote;
394
+ return await this._fetchLegacyDirectory();
395
+ }
396
+ /**
397
+ * Read the directory from the client Remote service. Returns undefined when
398
+ * no `remote.llm` namespace is present, so the caller can fall back.
399
+ */
400
+ async _fetchRemoteDirectory() {
401
+ const llm = this.remote ? this.remote.llm : null;
402
+ if (llm === null || llm === void 0) return void 0;
403
+ let providers = [];
404
+ let models = [];
405
+ try {
406
+ const [registered, declared] = await Promise.all([typeof llm.listProviders === "function" ? llm.listProviders() : void 0, typeof llm.listConfigurableProviders === "function" ? llm.listConfigurableProviders() : void 0]);
407
+ providers = joinProviderDirectory(registered && registered.ok === true && Array.isArray(registered.value) ? registered.value : [], declared && declared.ok === true && Array.isArray(declared.value) ? declared.value : []);
408
+ } catch {
409
+ providers = [];
410
+ }
411
+ const session = this.remote ? this.remote.session : null;
412
+ if (session && typeof session.modelCatalog === "function") try {
413
+ const response = await session.modelCatalog();
414
+ const value = response && response.ok === true ? response.value : void 0;
415
+ if (value && Array.isArray(value.groups)) models = value.groups;
416
+ } catch {
417
+ models = [];
418
+ }
419
+ return {
420
+ providers,
421
+ models
422
+ };
423
+ }
424
+ /** Legacy directory read for hosts whose transport still exposes `api.llm`. */
425
+ async _fetchLegacyDirectory() {
340
426
  if (this.llm === null || this.llm === void 0) return {
341
427
  providers: [],
342
428
  models: []
@@ -472,6 +558,60 @@ window.__ModuleLoader__.load({
472
558
  }
473
559
  };
474
560
  //#endregion
561
+ //#region src/client/ui/Chip.js
562
+ /**
563
+ * YOLO mode status chip (slot `conversation.input.left`). Shows "YOLO <preset>"
564
+ * and toggles the shared popup via store.togglePopup().
565
+ */
566
+ const chipStyle = {
567
+ display: "inline-flex",
568
+ alignItems: "center",
569
+ gap: 6,
570
+ padding: "4px 10px",
571
+ borderRadius: 999,
572
+ backgroundColor: "#ffffff",
573
+ color: "#1d4ed8",
574
+ border: "1px solid #93c5fd",
575
+ fontSize: 12,
576
+ cursor: "pointer",
577
+ whiteSpace: "nowrap",
578
+ userSelect: "none"
579
+ };
580
+ /** Status colour for the leading dot based on judge readiness. */
581
+ function dotColor(statusInfo) {
582
+ return statusInfo && statusInfo.judgeConfigured ? "#16a34a" : "#d97706";
583
+ }
584
+ /**
585
+ * @param {object} props - slot-delivered { store, useSnapshot, t } (inject face).
586
+ */
587
+ function Chip(props) {
588
+ const store = props.store;
589
+ const useSnapshot = props.useSnapshot;
590
+ if (store === void 0 || useSnapshot === void 0) return null;
591
+ return ChipLoaded({
592
+ store,
593
+ useSnapshot
594
+ });
595
+ }
596
+ function ChipLoaded({ store, useSnapshot }) {
597
+ (0, react.useEffect)(() => {
598
+ store.load();
599
+ }, [store]);
600
+ const statusInfo = useSnapshot((s) => s).statusInfo;
601
+ const preset = statusInfo && typeof statusInfo.preset === "string" ? statusInfo.preset : "…";
602
+ return (0, react.createElement)("button", {
603
+ style: chipStyle,
604
+ title: "YOLO",
605
+ onClick: () => store.togglePopup()
606
+ }, (0, react.createElement)("span", { style: {
607
+ width: 8,
608
+ height: 8,
609
+ borderRadius: 4,
610
+ backgroundColor: dotColor(statusInfo),
611
+ display: "inline-block"
612
+ } }), "YOLO " + preset);
613
+ }
614
+ //#endregion
475
615
  //#region src/client/presets.js
476
616
  /**
477
617
  * Static per-preset display data for the YOLO settings page: the level table
@@ -559,10 +699,12 @@ window.__ModuleLoader__.load({
559
699
  return PRESETS_INFO.find((preset) => preset.id === id) || null;
560
700
  }
561
701
  //#endregion
562
- //#region src/client/ui/SettingsSection.js
702
+ //#region src/client/ui/ConfigForm.js
563
703
  /**
564
- * YOLO mode settings section (slot `settings.section`). Renders the resolved
565
- * configuration (view.value) into a form and saves via path ops through
704
+ * YOLO mode configuration form. Rendered inside the plugin's own UI panel
705
+ * (`shell.overlay`, see ./Popup.js): the DSH `settings.section` page was retired
706
+ * so this form is the single configuration surface for the plugin. It renders
707
+ * the resolved configuration (view.value) and saves via path ops through
566
708
  * store.mutate with an optimistic-revision lock. Everything is React.createElement
567
709
  * + inline styles (no JSX, no CSS modules).
568
710
  *
@@ -747,12 +889,12 @@ window.__ModuleLoader__.load({
747
889
  }
748
890
  };
749
891
  /**
750
- * The YOLO settings section.
892
+ * The YOLO configuration form (hosted by the plugin's own UI panel).
751
893
  *
752
- * @param {object} props - slot-delivered inject face: { store, useSnapshot, t }
753
- * plus optional ownerProps.close.
894
+ * @param {object} props - panel-delivered inject face: { store, useSnapshot, t }
895
+ * plus optional close() to dismiss the hosting panel.
754
896
  */
755
- function SettingsSection(props) {
897
+ function ConfigForm(props) {
756
898
  const { store, useSnapshot, t, close } = props;
757
899
  if (store === void 0 || useSnapshot === void 0 || t === void 0) return null;
758
900
  return ReactSection({
@@ -1012,69 +1154,20 @@ window.__ModuleLoader__.load({
1012
1154
  return ops;
1013
1155
  }
1014
1156
  //#endregion
1015
- //#region src/client/ui/Chip.js
1016
- /**
1017
- * YOLO mode status chip (slot `conversation.input.left`). Shows "YOLO <preset>"
1018
- * and toggles the shared popup via store.togglePopup().
1019
- */
1020
- const chipStyle = {
1021
- display: "inline-flex",
1022
- alignItems: "center",
1023
- gap: 6,
1024
- padding: "4px 10px",
1025
- borderRadius: 999,
1026
- backgroundColor: "#ffffff",
1027
- color: "#1d4ed8",
1028
- border: "1px solid #93c5fd",
1029
- fontSize: 12,
1030
- cursor: "pointer",
1031
- whiteSpace: "nowrap",
1032
- userSelect: "none"
1033
- };
1034
- /** Status colour for the leading dot based on judge readiness. */
1035
- function dotColor(statusInfo) {
1036
- return statusInfo && statusInfo.judgeConfigured ? "#16a34a" : "#d97706";
1037
- }
1038
- /**
1039
- * @param {object} props - slot-delivered { store, useSnapshot, t } (inject face).
1040
- */
1041
- function Chip(props) {
1042
- const store = props.store;
1043
- const useSnapshot = props.useSnapshot;
1044
- if (store === void 0 || useSnapshot === void 0) return null;
1045
- return ChipLoaded({
1046
- store,
1047
- useSnapshot
1048
- });
1049
- }
1050
- function ChipLoaded({ store, useSnapshot }) {
1051
- (0, react.useEffect)(() => {
1052
- store.load();
1053
- }, [store]);
1054
- const statusInfo = useSnapshot((s) => s).statusInfo;
1055
- const preset = statusInfo && typeof statusInfo.preset === "string" ? statusInfo.preset : "…";
1056
- return (0, react.createElement)("button", {
1057
- style: chipStyle,
1058
- title: "YOLO",
1059
- onClick: () => store.togglePopup()
1060
- }, (0, react.createElement)("span", { style: {
1061
- width: 8,
1062
- height: 8,
1063
- borderRadius: 4,
1064
- backgroundColor: dotColor(statusInfo),
1065
- display: "inline-block"
1066
- } }), "YOLO " + preset);
1067
- }
1068
- //#endregion
1069
1157
  //#region src/client/ui/Popup.js
1070
1158
  /**
1071
- * YOLO mode status popup (slot `shell.overlay`). Renders null while the store's
1072
- * open flag is false; otherwise a stats card plus the recent decisions table
1073
- * (paged, PAGE_SIZE per page, newest first), an "open audit log" button, and a
1074
- * refresh button.
1159
+ * YOLO mode panel (slot `shell.overlay`). Renders null while the store's open
1160
+ * flag is false. When open it hosts TWO tabs and is the plugin's single
1161
+ * configuration surface (the DSH `settings.section` page was retired):
1162
+ *
1163
+ * - `config` (default): the full configuration form (preset / modes / judge
1164
+ * provider+model / timeout / maxTokens / concurrency / system prompt /
1165
+ * levels), reusing ./ConfigForm.js.
1166
+ * - `status`: the stats card, the recent decisions table (paged, PAGE_SIZE per
1167
+ * page, newest first), the "open audit log" button and a refresh button.
1075
1168
  *
1076
- * Guard/hook split mirrors SettingsSection: the outer component validates the
1077
- * slot inject face, the inner component owns all hooks unconditionally.
1169
+ * Guard/hook split mirrors ConfigForm: the outer component validates the slot
1170
+ * inject face, the inner component owns all hooks unconditionally.
1078
1171
  */
1079
1172
  /** Rows per page of the recent-decisions table. */
1080
1173
  const PAGE_SIZE = 5;
@@ -1191,6 +1284,35 @@ window.__ModuleLoader__.load({
1191
1284
  };
1192
1285
  const logResultOk = { color: "#16a34a" };
1193
1286
  const logResultErr = { color: "#dc2626" };
1287
+ /** Body wrapper for the status tab (the config tab renders ConfigForm directly). */
1288
+ const statusBodyStyle = {
1289
+ display: "flex",
1290
+ flexDirection: "column",
1291
+ gap: 12
1292
+ };
1293
+ const tabStyle = {
1294
+ backgroundColor: "#ffffff",
1295
+ color: "#374151",
1296
+ border: "1px solid #cbd5e1",
1297
+ borderRadius: 6,
1298
+ padding: "5px 12px",
1299
+ fontSize: 12,
1300
+ cursor: "pointer"
1301
+ };
1302
+ const tabActiveStyle = {
1303
+ backgroundColor: "#2563eb",
1304
+ color: "#ffffff",
1305
+ borderColor: "#2563eb"
1306
+ };
1307
+ /** One panel tab switch; the active tab is highlighted and non-submit. */
1308
+ function tabButton(t, id, current, setTab) {
1309
+ return (0, react.createElement)("button", {
1310
+ key: id,
1311
+ type: "button",
1312
+ style: Object.assign({}, tabStyle, id === current ? tabActiveStyle : {}),
1313
+ onClick: () => setTab(id)
1314
+ }, t(id === "config" ? "tabConfig" : "tabStatus"));
1315
+ }
1194
1316
  /** Truncate long reason text for display. */
1195
1317
  function truncate(text, max) {
1196
1318
  if (typeof text !== "string") return "";
@@ -1212,6 +1334,7 @@ window.__ModuleLoader__.load({
1212
1334
  }
1213
1335
  function ReactPopup({ store, useSnapshot, t }) {
1214
1336
  const state = useSnapshot((s) => s);
1337
+ const [tab, setTab] = (0, react.useState)("config");
1215
1338
  const [page, setPage] = (0, react.useState)(0);
1216
1339
  const [logResult, setLogResult] = (0, react.useState)(void 0);
1217
1340
  const [logBusy, setLogBusy] = (0, react.useState)(false);
@@ -1249,43 +1372,55 @@ window.__ModuleLoader__.load({
1249
1372
  setLogBusy(false);
1250
1373
  }
1251
1374
  };
1252
- return (0, react.createElement)("div", { style: popupStyle }, (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("strong", {}, t("chip")), (0, react.createElement)("div", { style: headerButtons }, (0, react.createElement)("button", {
1375
+ const statusBody = [
1376
+ logResult !== void 0 ? (0, react.createElement)("p", { style: Object.assign({}, captionStyle, logResult.ok ? logResultOk : logResultErr) }, logResult.ok ? t("openLogOk") + (logResult.value && logResult.value.path ? ": " + logResult.value.path : "") : logMessage(t, logResult.error)) : auditFile !== void 0 ? (0, react.createElement)("p", { style: captionStyle }, t("logPath") + ": " + auditFile) : null,
1377
+ (0, react.createElement)("div", { style: statGrid }, statCellOf(t, "statsTotal", stats.total), statCellOf(t, "statsAllowed", stats.allowed), statCellOf(t, "statsRejected", stats.rejected), statCellOf(t, "statsDelegated", stats.delegated)),
1378
+ recent.length === 0 ? (0, react.createElement)("p", { style: {
1379
+ color: "#6b7280",
1380
+ fontSize: 12,
1381
+ margin: 0
1382
+ } }, t("recentEmpty")) : (0, react.createElement)("table", { style: tableStyle }, (0, react.createElement)("thead", {}, (0, react.createElement)("tr", {}, (0, react.createElement)("th", { style: thStyle }, t("tableTime")), (0, react.createElement)("th", { style: thStyle }, t("tableTool")), (0, react.createElement)("th", { style: thStyle }, t("tableTarget")), (0, react.createElement)("th", { style: thStyle }, t("tableDecision")), (0, react.createElement)("th", { style: thStyle }, t("tableOutcome")), (0, react.createElement)("th", { style: thStyle }, t("tableReason")))), (0, react.createElement)("tbody", {}, pageRows.map((row, idx) => tableRow(t, row, safePage * PAGE_SIZE + idx)))),
1383
+ totalPages > 1 ? (0, react.createElement)("div", { style: {
1384
+ display: "flex",
1385
+ alignItems: "center",
1386
+ justifyContent: "flex-end",
1387
+ gap: 8
1388
+ } }, (0, react.createElement)("button", {
1389
+ style: Object.assign({}, pagerButton, safePage === 0 ? pagerButtonDisabled : {}),
1390
+ disabled: safePage === 0,
1391
+ onClick: () => setPage(safePage - 1)
1392
+ }, t("pagePrev")), (0, react.createElement)("span", { style: {
1393
+ fontSize: 12,
1394
+ color: "#374151"
1395
+ } }, t("pageOf", {
1396
+ current: String(safePage + 1),
1397
+ total: String(totalPages)
1398
+ })), (0, react.createElement)("button", {
1399
+ style: Object.assign({}, pagerButton, safePage >= totalPages - 1 ? pagerButtonDisabled : {}),
1400
+ disabled: safePage >= totalPages - 1,
1401
+ onClick: () => setPage(safePage + 1)
1402
+ }, t("pageNext"))) : null,
1403
+ (0, react.createElement)("div", { style: {
1404
+ display: "flex",
1405
+ justifyContent: "flex-end"
1406
+ } }, (0, react.createElement)("button", {
1407
+ style: primaryButton,
1408
+ onClick: () => store.togglePopup()
1409
+ }, t("close")))
1410
+ ];
1411
+ return (0, react.createElement)("div", { style: popupStyle }, (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("strong", {}, t("chip")), (0, react.createElement)("div", { style: headerButtons }, tabButton(t, "config", tab, setTab), tabButton(t, "status", tab, setTab), tab === "status" ? (0, react.createElement)("button", {
1253
1412
  style: logBusy ? Object.assign({}, secondaryButton, { opacity: .6 }) : secondaryButton,
1254
1413
  onClick: () => void openLog(),
1255
1414
  title: auditFile !== void 0 ? auditFile : void 0
1256
- }, logBusy ? t("openLogBusy") : t("openLog")), (0, react.createElement)("button", {
1415
+ }, logBusy ? t("openLogBusy") : t("openLog")) : null, (0, react.createElement)("button", {
1257
1416
  style: primaryButton,
1258
1417
  onClick: () => void store.load()
1259
- }, t("refresh")))), logResult !== void 0 ? (0, react.createElement)("p", { style: Object.assign({}, captionStyle, logResult.ok ? logResultOk : logResultErr) }, logResult.ok ? t("openLogOk") + (logResult.value && logResult.value.path ? ": " + logResult.value.path : "") : logMessage(t, logResult.error)) : auditFile !== void 0 ? (0, react.createElement)("p", { style: captionStyle }, t("logPath") + ": " + auditFile) : null, (0, react.createElement)("div", { style: statGrid }, statCellOf(t, "statsTotal", stats.total), statCellOf(t, "statsAllowed", stats.allowed), statCellOf(t, "statsRejected", stats.rejected), statCellOf(t, "statsDelegated", stats.delegated)), recent.length === 0 ? (0, react.createElement)("p", { style: {
1260
- color: "#6b7280",
1261
- fontSize: 12,
1262
- margin: 0
1263
- } }, t("recentEmpty")) : (0, react.createElement)("table", { style: tableStyle }, (0, react.createElement)("thead", {}, (0, react.createElement)("tr", {}, (0, react.createElement)("th", { style: thStyle }, t("tableTime")), (0, react.createElement)("th", { style: thStyle }, t("tableTool")), (0, react.createElement)("th", { style: thStyle }, t("tableTarget")), (0, react.createElement)("th", { style: thStyle }, t("tableDecision")), (0, react.createElement)("th", { style: thStyle }, t("tableOutcome")), (0, react.createElement)("th", { style: thStyle }, t("tableReason")))), (0, react.createElement)("tbody", {}, pageRows.map((row, idx) => tableRow(t, row, safePage * PAGE_SIZE + idx)))), totalPages > 1 ? (0, react.createElement)("div", { style: {
1264
- display: "flex",
1265
- alignItems: "center",
1266
- justifyContent: "flex-end",
1267
- gap: 8
1268
- } }, (0, react.createElement)("button", {
1269
- style: Object.assign({}, pagerButton, safePage === 0 ? pagerButtonDisabled : {}),
1270
- disabled: safePage === 0,
1271
- onClick: () => setPage(safePage - 1)
1272
- }, t("pagePrev")), (0, react.createElement)("span", { style: {
1273
- fontSize: 12,
1274
- color: "#374151"
1275
- } }, t("pageOf", {
1276
- current: String(safePage + 1),
1277
- total: String(totalPages)
1278
- })), (0, react.createElement)("button", {
1279
- style: Object.assign({}, pagerButton, safePage >= totalPages - 1 ? pagerButtonDisabled : {}),
1280
- disabled: safePage >= totalPages - 1,
1281
- onClick: () => setPage(safePage + 1)
1282
- }, t("pageNext"))) : null, (0, react.createElement)("div", { style: {
1283
- display: "flex",
1284
- justifyContent: "flex-end"
1285
- } }, (0, react.createElement)("button", {
1286
- style: primaryButton,
1287
- onClick: () => store.togglePopup()
1288
- }, t("close"))));
1418
+ }, t("refresh")))), tab === "config" ? (0, react.createElement)(ConfigForm, {
1419
+ store,
1420
+ useSnapshot,
1421
+ t,
1422
+ close: () => store.togglePopup()
1423
+ }) : (0, react.createElement)("div", { style: statusBodyStyle }, ...statusBody));
1289
1424
  }
1290
1425
  /** User-facing message for a failed open-log attempt (by error code). */
1291
1426
  function logMessage(t, error) {
@@ -1341,25 +1476,28 @@ window.__ModuleLoader__.load({
1341
1476
  /**
1342
1477
  * YOLO mode — browser half (DSH client plugin).
1343
1478
  *
1344
- * Registers the `settings.section` page (id 'yolo-mode') that configures the
1345
- * preset/modes/judge/levels, the `conversation.input.left` status chip, and the
1346
- * `shell.overlay` stats/recent popup. Data flows through the connection's
1347
- * generic RPC channel (/yolo-mode) into a snapshot store; writes travel as path
1348
- * ops through settingsMutate with an optimistic-revision lock.
1479
+ * Registers the `conversation.input.left` status chip and the `shell.overlay`
1480
+ * panel. ALL configuration (preset/modes/judge/levels) lives in that panel: the
1481
+ * `settings.section` page was retired, so nothing is configured from the DSH
1482
+ * Settings surface any more. Data flows through the connection's generic RPC
1483
+ * channel (/yolo-mode) into a snapshot store; writes travel as path ops through
1484
+ * settingsMutate with an optimistic-revision lock.
1349
1485
  */
1350
1486
  /**
1351
1487
  * Services required by these slot registrations (dsh.client.inject is the same
1352
- * short-name set): slots, locale, connection, remote.
1488
+ * short-name set): slots, locale, connection, remote, remote.llm. The dotted
1489
+ * `remote.llm` namespace carries the provider/model directory on 0.1.7+.
1353
1490
  */
1354
1491
  const inject = [
1355
1492
  "slots",
1356
1493
  "locale",
1357
1494
  "connection",
1358
- "remote"
1495
+ "remote",
1496
+ "remote.llm"
1359
1497
  ];
1360
1498
  /**
1361
- * Register the YOLO section/chip/popup once the slot declarations are on the
1362
- * ledger, wire the store to the connection, and keep it fresh on every pushed
1499
+ * Register the YOLO chip/panel once the slot declarations are on the ledger,
1500
+ * wire the store to the connection, and keep it fresh on every pushed
1363
1501
  * invalidation.
1364
1502
  *
1365
1503
  * @param {object} ctx - client cordis context.
@@ -1374,6 +1512,7 @@ window.__ModuleLoader__.load({
1374
1512
  const t = ctx.locale.bind(NS);
1375
1513
  const store = new YoloStore({
1376
1514
  rpc: connection.rpc,
1515
+ remote: ctx.get("remote") ?? null,
1377
1516
  llm: connection.api ? connection.api.llm : null
1378
1517
  });
1379
1518
  const useSnapshot = bindSnapshotSelector(store);
@@ -1396,14 +1535,6 @@ window.__ModuleLoader__.load({
1396
1535
  useSnapshot,
1397
1536
  t
1398
1537
  });
1399
- ctx.slots.inject("settings.section", () => ctx.slots.register({
1400
- name: "settings.section",
1401
- id: "yolo-mode",
1402
- order: 25,
1403
- label: () => t("nav"),
1404
- locale: NS,
1405
- inject: injected
1406
- }, SettingsSection));
1407
1538
  ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
1408
1539
  name: "conversation.input.left",
1409
1540
  id: "yolo-mode-chip",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-yolo-mode",
3
- "version": "0.5.2-beta.0",
3
+ "version": "0.6.0",
4
4
  "description": "dsh-yolo-mode —— DeepSeek Harness 双面包插件:当会话处于可写沙箱模式且审批策略为 ask 时,用大模型自动裁决沙箱升权申请,支持内置预设与自定义权限层级,并提供宿主 settings + 自发布设置桥(/yolo-mode)与 Web 客户端 UI。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -44,8 +44,6 @@
44
44
  "@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
45
45
  "@deepseek-ai/dsh-timeout": "^0.1.7-rc.1",
46
46
  "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
47
- "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.2",
48
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
49
47
  "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.1",
50
48
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
51
49
  "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
@@ -54,9 +52,9 @@
54
52
  "devDependencies": {
55
53
  "rolldown": "^1.2.4",
56
54
  "@deepseek-ai/cordis": "^4.0.2",
57
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
58
- "@deepseek-ai/dsh-timeout": "^0.1.7-rc.1",
59
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
55
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
56
+ "@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
57
+ "@deepseek-ai/dsh-settings": "0.1.7-rc.2",
60
58
  "@deepseek-ai/schemastery": "^3.18.1"
61
59
  },
62
60
  "scripts": {
@@ -74,7 +72,8 @@
74
72
  "slots",
75
73
  "locale",
76
74
  "connection",
77
- "remote"
75
+ "remote",
76
+ "remote.llm"
78
77
  ]
79
78
  },
80
79
  "compatibility": {
@@ -82,9 +81,10 @@
82
81
  "0.1.5-rc.1": "compatible",
83
82
  "0.1.5-rc.2": "compatible",
84
83
  "0.1.6-alpha.2": "compatible",
85
- "0.1.7-rc.1": "compatible"
84
+ "0.1.7-rc.1": "compatible",
85
+ "0.1.7-rc.2": "compatible"
86
86
  },
87
87
  "node": ">=20"
88
88
  }
89
89
  }
90
- }
90
+ }