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 +70 -9
- package/README.md +4 -2
- package/lib/client/index.js +257 -126
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,17 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Semantic Versioning](https://semver.org/)。
|
|
4
4
|
|
|
5
|
-
## [0.
|
|
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
|
-
###
|
|
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
|
-
-
|
|
10
|
-
|
|
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
|
[](https://www.npmjs.com/package/dsh-yolo-mode)
|
|
8
8
|
[](LICENSE)
|
|
9
|
-
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
9
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
10
10
|
[](https://awesome-dsh-plugin.com)
|
|
11
11
|
|
|
12
|
-
>
|
|
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
|
|
package/lib/client/index.js
CHANGED
|
@@ -15,7 +15,8 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
const NS = "settings.yoloMode";
|
|
16
16
|
/** English dictionary. */
|
|
17
17
|
const en = {
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
307
|
+
/** Provider directory: remote.llm joined rows, or legacy llm.providers. */
|
|
270
308
|
providers: [],
|
|
271
|
-
/** Model catalog groups
|
|
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)
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
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/
|
|
702
|
+
//#region src/client/ui/ConfigForm.js
|
|
563
703
|
/**
|
|
564
|
-
* YOLO mode
|
|
565
|
-
*
|
|
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
|
|
892
|
+
* The YOLO configuration form (hosted by the plugin's own UI panel).
|
|
751
893
|
*
|
|
752
|
-
* @param {object} props -
|
|
753
|
-
* plus optional
|
|
894
|
+
* @param {object} props - panel-delivered inject face: { store, useSnapshot, t }
|
|
895
|
+
* plus optional close() to dismiss the hosting panel.
|
|
754
896
|
*/
|
|
755
|
-
function
|
|
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
|
|
1072
|
-
*
|
|
1073
|
-
* (
|
|
1074
|
-
*
|
|
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
|
|
1077
|
-
*
|
|
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
|
-
|
|
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")))),
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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 `
|
|
1345
|
-
* preset/modes/judge/levels
|
|
1346
|
-
* `
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
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
|
|
1362
|
-
*
|
|
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.
|
|
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": "
|
|
58
|
-
"@deepseek-ai/dsh-timeout": "
|
|
59
|
-
"@deepseek-ai/dsh-settings": "
|
|
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
|
+
}
|