dsh-yolo-mode 0.5.1 → 0.5.3
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 +82 -0
- package/README.md +4 -2
- package/lib/client/index.js +97 -10
- package/lib/index.js +21 -29
- package/lib/remote.js +7 -6
- package/lib/settings.js +66 -38
- package/package.json +15 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Semantic Versioning](https://semver.org/)。
|
|
4
4
|
|
|
5
|
+
## [0.5.3] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### 兼容:适配 DSH 0.1.7-rc.2
|
|
8
|
+
|
|
9
|
+
- 在本机 **0.1.7-rc.2** 上真实装载运行通过:全部入口激活、零错误、无 pending
|
|
10
|
+
(`did not activate` 告警不出现);设置读写(`settingsView` / `settingsMutate`
|
|
11
|
+
乐观锁)实测往返成功。`dsh.compatibility.dshReleases` 新增
|
|
12
|
+
`"0.1.7-rc.2": "compatible"`;README 徽章与兼容性说明同步。
|
|
13
|
+
- 0.1.7-rc.1 → rc.2 的官方 `@deepseek-ai/dsh-*` 公开 API **无移除、无改名**
|
|
14
|
+
(`dsh-settings` / `dsh-client-connection` / `dsh-client-ui-slots` 逐字节相同,
|
|
15
|
+
其余为增量新增),本插件无源码适配需求。
|
|
16
|
+
|
|
17
|
+
### 修复:设置页「供应商」下拉为空
|
|
18
|
+
|
|
19
|
+
设置页 `judgeProvider` 下拉始终为空。根因:0.1.7 起客户端 `connection.api.llm`
|
|
20
|
+
已不存在,store 构造时 `this.llm` 恒为 `null`,`_fetchLlmDirectory()` 直接返回空数组。
|
|
21
|
+
本次改为经客户端 Remote 服务读取:
|
|
22
|
+
|
|
23
|
+
- **供应商**:`ctx.remote.llm.listProviders()`(已注册路由)与
|
|
24
|
+
`listConfigurableProviders()`(已声明可配置)经 `joinProviderDirectory` 合并为与官方
|
|
25
|
+
settings-models 页一致的 `{provider, displayName}` 行。
|
|
26
|
+
- **模型**:`ctx.remote.session.modelCatalog()` 的 `groups`(按 provider 分组),
|
|
27
|
+
与官方同一数据源。
|
|
28
|
+
- **inject**:`package.json` 的 `dsh.client.inject` 与客户端 `inject` 数组新增
|
|
29
|
+
`"remote.llm"`。
|
|
30
|
+
- **降级保留**:无 `remote.llm` 命名空间时回退旧 `connection.api.llm`;目录读取失败
|
|
31
|
+
不致命,仍回退自由文本输入(既有设计不变)。
|
|
32
|
+
|
|
33
|
+
### 测试
|
|
34
|
+
|
|
35
|
+
- 新增 `store-logic.joinProviderDirectory` 与 4 项 `YoloStore` `remote.llm` /
|
|
36
|
+
`remote.session` 单测(目录填充、失败非致命、legacy 回退、remote 优先);
|
|
37
|
+
`npm test` 137 项全绿。
|
|
38
|
+
- 本机无浏览器,客户端 UI 未在浏览器中实际渲染验证;已核对 `npm pack` 产物经
|
|
39
|
+
0.1.7-rc.2 profile 装载后**被服务的**客户端 bundle 含 `remote.llm.listProviders`
|
|
40
|
+
/ `listConfigurableProviders` / `session.modelCatalog`,且宿主 `llm/listProviders`、
|
|
41
|
+
`session/modelCatalog` 实测返回非空目录(供应商下拉将填充 5 项)。
|
|
42
|
+
|
|
43
|
+
### Beta 说明
|
|
44
|
+
|
|
45
|
+
- 测试版 `0.5.2-beta.0`(`--tag beta`)的内容——设置子系统迁移至 DSH 0.1.7 的
|
|
46
|
+
`SettingsForms`——随本版一并正式发布,详见下方 `0.5.2` 条目。
|
|
47
|
+
|
|
48
|
+
## [0.5.2] - 2026-09-24
|
|
49
|
+
|
|
50
|
+
### 兼容:设置子系统迁移至 DSH 0.1.7-rc.1 的 `SettingsForms`
|
|
51
|
+
|
|
52
|
+
DSH 0.1.7 移除了 `@deepseek-ai/dsh-settings` 的 `SettingsProvider.installSection`
|
|
53
|
+
(默认导出改为 `SettingsForms`),本插件原设置子系统因此在新版抛
|
|
54
|
+
`TypeError: settings.installSection is not a function` 而无法激活。本次按官方新模型重写:
|
|
55
|
+
|
|
56
|
+
- **设置声明 → 插件 Config schema**(`lib/settings.js` 的 `YoloSettingsSchema`,
|
|
57
|
+
经 `lib/index.js` 的 `export const Config` 暴露):每个顶层字段标注
|
|
58
|
+
`.volatile()`,声明为可热更——写回经 loader 的就地更新,插件无需重启即可生效
|
|
59
|
+
(与旧模型「只改某些字段不重启」行为一致)。
|
|
60
|
+
- **设置读取 → `config.<field>.get()`**(`readYoloConfig`):`effectiveConfig()`
|
|
61
|
+
每次裁决读取实时解析值(schema 默认 + 条目 config),再 `normalizeConfig`。
|
|
62
|
+
- **页面策略 → `settings.configure({ auto: false }, fiber)`**
|
|
63
|
+
(`installYoloSettingsPage`):插件自带 Web 设置页,`auto:false` 禁止宿主再自动
|
|
64
|
+
生成重复页面;settings 服务缺失时零侵入跳过。
|
|
65
|
+
- **自发布设置桥与客户端不变**:`lib/remote.js` 的 `settingsView` / `settingsMutate`
|
|
66
|
+
端点本就建立在 `describe` / `mutate` / `writable` / `SettingsConflictError` 之上,
|
|
67
|
+
这些方法面在 `SettingsForms` 中保持不变,端点形状与乐观锁语义无需改动,客户端
|
|
68
|
+
无需改动。
|
|
69
|
+
- **双层 → 单层映射**:旧「插件行 config 为 base 层 + settings.yaml 用户层」映射为
|
|
70
|
+
0.1.7「Profile 插件条目 config 单层」;旧 `settings.yaml` 由
|
|
71
|
+
`SettingsForms.importLegacyDocument` 一次性导入到 Profile。
|
|
72
|
+
|
|
73
|
+
### 兼容性
|
|
74
|
+
|
|
75
|
+
- `peerDependencies` 的 `@deepseek-ai/dsh*` 下限提升至 `^0.1.7-rc.1`(`dsh-host-apiproxy`
|
|
76
|
+
/ `dsh-client-runtime` 两个历史例外保持 `^0.1.1-rc.2`)。
|
|
77
|
+
- `dsh.compatibility.dshReleases` 增列 `0.1.7-rc.1: compatible`。
|
|
78
|
+
- README 徽章与兼容性行同步至 0.1.7-rc.1。
|
|
79
|
+
|
|
80
|
+
### 测试
|
|
81
|
+
|
|
82
|
+
- `test/probe.test.mjs` 重写至 0.1.7 API:真实 cordis 经 Config schema 解析条目
|
|
83
|
+
config、断言 `configure({auto:false})` 被调用、并用 cosmokit `updateVolatile`
|
|
84
|
+
(loader `_commitVolatile` 的同一原语)验证 volatile 就地更新后裁决即时生效。
|
|
85
|
+
- 其余测试文件保持通过;`npm test` 132 项全绿。
|
|
86
|
+
|
|
5
87
|
## [0.5.1] - 2026-09-22
|
|
6
88
|
|
|
7
89
|
### 修复:裁判对推理型模型恒失败(Issue #1)
|
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)
|
|
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
|
@@ -234,6 +234,42 @@ window.__ModuleLoader__.load({
|
|
|
234
234
|
if (code === "settings-rejected" || code === "schema-validation") return "rejected";
|
|
235
235
|
return "fatal";
|
|
236
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Join the live provider routes (`remote.llm.listProviders`, rows `{id, name}`)
|
|
239
|
+
* with the declared configurable providers
|
|
240
|
+
* (`remote.llm.listConfigurableProviders`, rows `{provider, displayName, …}`)
|
|
241
|
+
* into one directory of `{ provider, displayName }` rows the select consumes.
|
|
242
|
+
* Declared entries lead (so a configured-but-not-yet-registered provider still
|
|
243
|
+
* appears), then any registered route the declaration does not name. Mirrors
|
|
244
|
+
* the official settings-models `joinProviderDirectory`.
|
|
245
|
+
*/
|
|
246
|
+
function joinProviderDirectory(registered, declared) {
|
|
247
|
+
const routes = Array.isArray(registered) ? registered : [];
|
|
248
|
+
const directory = Array.isArray(declared) ? declared : [];
|
|
249
|
+
const rows = [];
|
|
250
|
+
const named = /* @__PURE__ */ new Set();
|
|
251
|
+
for (const entry of directory) {
|
|
252
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
253
|
+
if (typeof entry.provider !== "string" || entry.provider === "") continue;
|
|
254
|
+
named.add(entry.provider);
|
|
255
|
+
rows.push({
|
|
256
|
+
provider: entry.provider,
|
|
257
|
+
displayName: typeof entry.displayName === "string" && entry.displayName !== "" ? entry.displayName : void 0,
|
|
258
|
+
active: routes.some((route) => route && route.id === entry.provider)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
for (const route of routes) {
|
|
262
|
+
if (route === null || typeof route !== "object") continue;
|
|
263
|
+
if (typeof route.id !== "string" || route.id === "") continue;
|
|
264
|
+
if (named.has(route.id)) continue;
|
|
265
|
+
rows.push({
|
|
266
|
+
provider: route.id,
|
|
267
|
+
displayName: typeof route.name === "string" && route.name !== "" ? route.name : void 0,
|
|
268
|
+
active: true
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return rows;
|
|
272
|
+
}
|
|
237
273
|
//#endregion
|
|
238
274
|
//#region src/client/store.js
|
|
239
275
|
/**
|
|
@@ -266,9 +302,9 @@ window.__ModuleLoader__.load({
|
|
|
266
302
|
writable: true,
|
|
267
303
|
error: void 0,
|
|
268
304
|
open: false,
|
|
269
|
-
/**
|
|
305
|
+
/** Provider directory: remote.llm joined rows, or legacy llm.providers. */
|
|
270
306
|
providers: [],
|
|
271
|
-
/** Model catalog groups
|
|
307
|
+
/** Model catalog groups: remote.session.modelCatalog, or legacy llm.models. */
|
|
272
308
|
models: []
|
|
273
309
|
};
|
|
274
310
|
}
|
|
@@ -279,14 +315,20 @@ window.__ModuleLoader__.load({
|
|
|
279
315
|
* @param {{ call: (channel: string, endpoint: string, payload: any) => Promise<any> }} options.rpc
|
|
280
316
|
* rpc.call(channel, endpoint, payload) resolves to { ok: true, value } |
|
|
281
317
|
* { ok: false, error }.
|
|
282
|
-
* @param {object|null} [options.llm] - optional `connection.api.llm` face
|
|
318
|
+
* @param {object|null} [options.llm] - optional legacy `connection.api.llm` face
|
|
283
319
|
* ({ providers(), models() } returning { result: { ok, value } }); when null
|
|
284
320
|
* the directory stays empty and the judge fields fall back to free text.
|
|
321
|
+
* @param {object|null} [options.remote] - optional client Remote service; when
|
|
322
|
+
* it carries an `llm` namespace the directory comes from
|
|
323
|
+
* `llm.listProviders()` + `llm.listConfigurableProviders()` (0.1.7+) and the
|
|
324
|
+
* model catalog from `session.modelCatalog()`. Falls back to `llm` when the
|
|
325
|
+
* Remote namespace is absent.
|
|
285
326
|
*/
|
|
286
327
|
var YoloStore = class {
|
|
287
|
-
constructor({ rpc, llm }) {
|
|
328
|
+
constructor({ rpc, llm, remote }) {
|
|
288
329
|
this.rpc = rpc;
|
|
289
330
|
this.llm = llm == null ? null : llm;
|
|
331
|
+
this.remote = remote == null ? null : remote;
|
|
290
332
|
this._state = initialYoloState();
|
|
291
333
|
this._listeners = /* @__PURE__ */ new Set();
|
|
292
334
|
this._generation = 0;
|
|
@@ -331,12 +373,54 @@ window.__ModuleLoader__.load({
|
|
|
331
373
|
if (error !== null && typeof error === "object" && typeof error.code === "string") return error.code;
|
|
332
374
|
}
|
|
333
375
|
/**
|
|
334
|
-
* Fetch the optional LLM directory (provider list + model catalog)
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
376
|
+
* Fetch the optional LLM directory (provider list + model catalog).
|
|
377
|
+
*
|
|
378
|
+
* 0.1.7+ exposes it through the client Remote service: the provider directory
|
|
379
|
+
* is the join of `remote.llm.listProviders()` (registered routes) and
|
|
380
|
+
* `remote.llm.listConfigurableProviders()` (declared providers), and the model
|
|
381
|
+
* catalog is `remote.session.modelCatalog().value.groups` — the same sources
|
|
382
|
+
* the official settings-models page reads. Older hosts without a Remote `llm`
|
|
383
|
+
* namespace fall back to the legacy `connection.api.llm` face.
|
|
384
|
+
*
|
|
385
|
+
* Directory failures are NOT fatal: they leave the snapshot's providers/models
|
|
386
|
+
* empty without flipping status to 'error' — only the settings/status bridge
|
|
387
|
+
* calls can do that.
|
|
338
388
|
*/
|
|
339
389
|
async _fetchLlmDirectory() {
|
|
390
|
+
const fromRemote = await this._fetchRemoteDirectory();
|
|
391
|
+
if (fromRemote !== void 0) return fromRemote;
|
|
392
|
+
return await this._fetchLegacyDirectory();
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Read the directory from the client Remote service. Returns undefined when
|
|
396
|
+
* no `remote.llm` namespace is present, so the caller can fall back.
|
|
397
|
+
*/
|
|
398
|
+
async _fetchRemoteDirectory() {
|
|
399
|
+
const llm = this.remote ? this.remote.llm : null;
|
|
400
|
+
if (llm === null || llm === void 0) return void 0;
|
|
401
|
+
let providers = [];
|
|
402
|
+
let models = [];
|
|
403
|
+
try {
|
|
404
|
+
const [registered, declared] = await Promise.all([typeof llm.listProviders === "function" ? llm.listProviders() : void 0, typeof llm.listConfigurableProviders === "function" ? llm.listConfigurableProviders() : void 0]);
|
|
405
|
+
providers = joinProviderDirectory(registered && registered.ok === true && Array.isArray(registered.value) ? registered.value : [], declared && declared.ok === true && Array.isArray(declared.value) ? declared.value : []);
|
|
406
|
+
} catch {
|
|
407
|
+
providers = [];
|
|
408
|
+
}
|
|
409
|
+
const session = this.remote ? this.remote.session : null;
|
|
410
|
+
if (session && typeof session.modelCatalog === "function") try {
|
|
411
|
+
const response = await session.modelCatalog();
|
|
412
|
+
const value = response && response.ok === true ? response.value : void 0;
|
|
413
|
+
if (value && Array.isArray(value.groups)) models = value.groups;
|
|
414
|
+
} catch {
|
|
415
|
+
models = [];
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
providers,
|
|
419
|
+
models
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/** Legacy directory read for hosts whose transport still exposes `api.llm`. */
|
|
423
|
+
async _fetchLegacyDirectory() {
|
|
340
424
|
if (this.llm === null || this.llm === void 0) return {
|
|
341
425
|
providers: [],
|
|
342
426
|
models: []
|
|
@@ -1349,13 +1433,15 @@ window.__ModuleLoader__.load({
|
|
|
1349
1433
|
*/
|
|
1350
1434
|
/**
|
|
1351
1435
|
* Services required by these slot registrations (dsh.client.inject is the same
|
|
1352
|
-
* short-name set): slots, locale, connection, remote.
|
|
1436
|
+
* short-name set): slots, locale, connection, remote, remote.llm. The dotted
|
|
1437
|
+
* `remote.llm` namespace carries the provider/model directory on 0.1.7+.
|
|
1353
1438
|
*/
|
|
1354
1439
|
const inject = [
|
|
1355
1440
|
"slots",
|
|
1356
1441
|
"locale",
|
|
1357
1442
|
"connection",
|
|
1358
|
-
"remote"
|
|
1443
|
+
"remote",
|
|
1444
|
+
"remote.llm"
|
|
1359
1445
|
];
|
|
1360
1446
|
/**
|
|
1361
1447
|
* Register the YOLO section/chip/popup once the slot declarations are on the
|
|
@@ -1374,6 +1460,7 @@ window.__ModuleLoader__.load({
|
|
|
1374
1460
|
const t = ctx.locale.bind(NS);
|
|
1375
1461
|
const store = new YoloStore({
|
|
1376
1462
|
rpc: connection.rpc,
|
|
1463
|
+
remote: ctx.get("remote") ?? null,
|
|
1377
1464
|
llm: connection.api ? connection.api.llm : null
|
|
1378
1465
|
});
|
|
1379
1466
|
const useSnapshot = bindSnapshotSelector(store);
|
package/lib/index.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* 抢在 dsh-host-apiproxy 的人工应答者之前介入,对沙箱升权申请(
|
|
6
6
|
* `escalate sandbox to <mode>: <justification>`)按预设策略自动裁决。
|
|
7
7
|
*
|
|
8
|
-
* v0.3.0 重做(design.md §12.3
|
|
8
|
+
* v0.3.0 重做(design.md §12.3);v0.5.2 迁移至 0.1.7 的 SettingsForms:
|
|
9
9
|
* - `inject: ['llm','settings']`,用 `ctx.llm` / `ctx.settings`;
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
10
|
+
* - 设置声明为本插件的 Config schema(`export const Config`,volatile 字段可热更),
|
|
11
|
+
* `effectiveConfig()` 每次裁决用 `config.<field>.get()` 读取实时值后 `normalizeConfig`;
|
|
12
|
+
* - 页面策略经 `installYoloSettingsPage`(`settings.configure({auto:false})`)注册;
|
|
12
13
|
* - `getJudge()` 按 judge 配置键缓存裁判实例(用 `ctx.llm`),键变化重建;
|
|
13
14
|
* - 审计统计写入 lib/state.js 模块级单例(bridge 条目已读);
|
|
14
15
|
* - **不再注册任何 webServer 路由**(由独立 lib/bridge-entry.js 承担)。
|
|
@@ -28,13 +29,17 @@ import {
|
|
|
28
29
|
ESCALATION_RE,
|
|
29
30
|
} from './policy.js';
|
|
30
31
|
import { createJudge, defaultJudgePromptFor } from './judge.js';
|
|
31
|
-
import {
|
|
32
|
+
import { YoloSettingsSchema, installYoloSettingsPage, readYoloConfig, pruneEmpty } from './settings.js';
|
|
32
33
|
import { recordDecision, sessionOrigin } from './state.js';
|
|
33
34
|
import { resolveAuditFile } from './audit.js';
|
|
34
35
|
|
|
35
36
|
export const name = 'dsh-yolo-mode';
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* 插件 Config schema(0.1.7 起即权威设置 schema;条目 id `yolo-mode` 即
|
|
40
|
+
* `describe()`/`mutate()` 的 ns)。cordis 从本导出读取并用其解析条目 config。
|
|
41
|
+
*/
|
|
42
|
+
export const Config = YoloSettingsSchema;
|
|
38
43
|
|
|
39
44
|
/** 反向扫描会话事件,取与 req.callId 匹配的 tool/call 实参摘要(1200 字符截断)。 */
|
|
40
45
|
function extractArgumentsSummary(req, session) {
|
|
@@ -76,35 +81,22 @@ export const inject = ['llm', 'settings'];
|
|
|
76
81
|
* @param {import('@deepseek-ai/cordis').Context} ctx
|
|
77
82
|
* @param {object} [rawConfig] 插件行 config(全字段可选)
|
|
78
83
|
*/
|
|
79
|
-
export function apply(ctx,
|
|
80
|
-
// 行配置通过(fail-loud):非法配置立即抛错 → 加载失败。
|
|
81
|
-
const rowCfg = rawConfig ?? {};
|
|
82
|
-
normalizeConfig(rowCfg);
|
|
83
|
-
|
|
84
|
+
export function apply(ctx, config) {
|
|
84
85
|
const logger = ctx.logger('yolo-mode');
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
// installSettingsSection 在 settings 服务存在时,把 resolved scope 设为 active 源;
|
|
88
|
-
// 服务缺失/卸载时回退到 `entry`(插件行 config)。
|
|
89
|
-
let sourceThunk = undefined;
|
|
90
|
-
installYoloSettings(ctx, rowCfg, {
|
|
91
|
-
setSource: (thunk) => {
|
|
92
|
-
sourceThunk = thunk;
|
|
93
|
-
},
|
|
94
|
-
onChange: () => {
|
|
95
|
-
// resolved 已由 setSource 的 thunk 覆盖;judge 缓存按键在 getJudge 中自愈。
|
|
96
|
-
},
|
|
97
|
-
validate: validateYoloSettings,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/** 每次裁决读取的有效配置(resolved settings;未就绪时插件行 config)后规范化。 */
|
|
87
|
+
/** 每次裁决读取的有效配置(Config schema 默认 + 条目 config 实时值)后规范化。 */
|
|
101
88
|
function effectiveConfig() {
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
return normalizeConfig(pruneEmpty(raw));
|
|
89
|
+
// 解析值含 schema 空集合默认(modes:[]/levels:{}),必须先剔除再 normalizeConfig,
|
|
90
|
+
// 否则 fail-loud 拒绝空 modes(与 settings.js 的写时校验同一根因)。
|
|
91
|
+
return normalizeConfig(pruneEmpty(readYoloConfig(config)));
|
|
106
92
|
}
|
|
107
93
|
|
|
94
|
+
// 初始配置 fail-loud:非法配置立即抛错 → 加载失败(与旧 apply 行为一致)。
|
|
95
|
+
effectiveConfig();
|
|
96
|
+
|
|
97
|
+
// ---- settings 页面策略(0.1.7 SettingsForms;auto:false = 自建 UI)----
|
|
98
|
+
installYoloSettingsPage(ctx);
|
|
99
|
+
|
|
108
100
|
const auditFile = () => resolveAuditFile(effectiveConfig());
|
|
109
101
|
|
|
110
102
|
let dirEnsured = false;
|
package/lib/remote.js
CHANGED
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
/**
|
|
29
29
|
* 纯映射 / 线信封辅助在顶部(无 cordis),可在普通 node 环境单测。
|
|
30
30
|
*
|
|
31
|
-
* DSH 0.1.
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
* `
|
|
31
|
+
* DSH 0.1.7-rc.1 兼容:dsh-settings 默认导出由 `SettingsProvider` 改为
|
|
32
|
+
* `SettingsForms`,但其 `describe` / `writable` / `mutate` / `replace` /
|
|
33
|
+
* `update` 方法面与 `SettingsConflictError`(code 'SETTINGS_CONFLICT')不变,
|
|
34
|
+
* 故本桥的线协议与端点形状无需改动,仅内部实现改由 `SettingsForms` 承担。
|
|
35
|
+
* 命名空间仍为纯 kebab-case 字符串字面量(`yolo-mode`,等于插件条目 id)。
|
|
35
36
|
*
|
|
36
37
|
* @module lib/remote.js
|
|
37
38
|
*/
|
|
@@ -247,8 +248,8 @@ function yoloConflict(conflict) {
|
|
|
247
248
|
/**
|
|
248
249
|
* 对 settings seam 执行一次路径 op 变更并映射为 RpcResult(携带新 redacted view,
|
|
249
250
|
* 或 settings-conflict / settings-rejected 错误)。纯函数可注入 mutate/describe 测试。
|
|
250
|
-
*
|
|
251
|
-
*
|
|
251
|
+
* 0.1.7 起 `ns` 即插件条目 id,原样透传给 SettingsForms.mutate;非 volatile 路径
|
|
252
|
+
* 会被其拒绝(映射为 settings-rejected)。
|
|
252
253
|
* @param {Function} mutate settings.mutate 绑定
|
|
253
254
|
* @param {Function} describe settings.describe 绑定
|
|
254
255
|
* @param {string} ns 命名空间字符串
|
package/lib/settings.js
CHANGED
|
@@ -5,14 +5,18 @@
|
|
|
5
5
|
* `yolo-mode`,用 schemastery 宽松 schema 解析分区值,写时校验委托
|
|
6
6
|
* ./policy.js 的 `normalizeConfig`(fail-loud 抛错即拒绝写入)。
|
|
7
7
|
*
|
|
8
|
-
* 分层层(design.md §12.3
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* 分层层(design.md §12.3 + 0.1.7 单层迁移):0.1.7 起设置不再由
|
|
9
|
+
* settings.yaml 覆盖层承载,而是**当前 Profile 的插件条目 config 单层**——
|
|
10
|
+
* schemastery Config schema 的默认值即 base 层,用户在设置页的改动写回该
|
|
11
|
+
* 条目 config。故本模块导出的 `YoloSettingsSchema` 同时是插件 Config schema
|
|
12
|
+
* (见 lib/index.js 的 `export const Config`),每个可热更字段用 `.volatile()`
|
|
13
|
+
* 声明;读取用 `config.<field>.get()`。
|
|
11
14
|
*
|
|
12
|
-
* DSH 0.1.
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
* `ctx.settings.
|
|
15
|
+
* DSH 0.1.7-rc.1 兼容:dsh-settings 默认导出由 `SettingsProvider`
|
|
16
|
+
* (`installSection`)改为 `SettingsForms`(`describe/update/replace/mutate`)。
|
|
17
|
+
* `installSection` 已删除;页面策略改走
|
|
18
|
+
* `ctx.settings.configure({ auto }, ownerFiber)`(auto:false = 插件自建 UI)。
|
|
19
|
+
* 旧的 `settings.yaml` 由 `SettingsForms.importLegacyDocument` 一次性导入。
|
|
16
20
|
*
|
|
17
21
|
* 纯 JavaScript(ESM),宿主代码仅 import node: 内置与同包 peer
|
|
18
22
|
* (@deepseek-ai/dsh-settings、@deepseek-ai/schemastery)。
|
|
@@ -29,26 +33,53 @@ export const YOLO_SETTINGS_NAMESPACE = 'yolo-mode';
|
|
|
29
33
|
const PRESETS = ['off', 'strict', 'balanced', 'permissive', 'yolo', 'custom'];
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
|
-
* schemastery
|
|
33
|
-
*
|
|
34
|
-
*
|
|
36
|
+
* schemastery Config schema(0.1.7 起同时是插件的权威设置 schema):全字段可选
|
|
37
|
+
* (object 字段默认可选),使未写入的空白分区解析为空对象,绝不泄漏默认值覆盖
|
|
38
|
+
* 条目 config 的普通层。每个顶层字段用 `.volatile()` 声明为可热更——设置页的
|
|
39
|
+
* 写回经 loader 的 `_commitVolatile` 就地更新引用,插件无需重启;这与旧模型
|
|
40
|
+
* "只改某些字段不重启插件" 的行为一致。
|
|
41
|
+
*
|
|
42
|
+
* 注意:`.volatile()` 不可嵌套(schemastery 校验"volatile 字段需固定对象路径"),
|
|
43
|
+
* 故只标注顶层字段;其子路径由 `isVolatilePath` 视作可热更。
|
|
44
|
+
*
|
|
45
|
+
* 真正的形状/语义校验由写时 `validateYoloSettings = normalizeConfig` 完成(fail-loud)
|
|
46
|
+
* 以及 schemastery 在 configEditor 写回时的解析校验。
|
|
35
47
|
*/
|
|
36
48
|
export const YoloSettingsSchema = z.object({
|
|
37
|
-
preset: z.union([...PRESETS]).default('balanced'),
|
|
38
|
-
modes: z.array(z.string()),
|
|
39
|
-
levels: z.dict(z.any()),
|
|
40
|
-
judge: z
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
preset: z.union([...PRESETS]).default('balanced').volatile(),
|
|
50
|
+
modes: z.array(z.string()).volatile(),
|
|
51
|
+
levels: z.dict(z.any()).volatile(),
|
|
52
|
+
judge: z
|
|
53
|
+
.object({
|
|
54
|
+
provider: z.string(),
|
|
55
|
+
model: z.string(),
|
|
56
|
+
systemPrompt: z.string(),
|
|
57
|
+
timeoutMs: z.natural(),
|
|
58
|
+
maxTokens: z.natural(),
|
|
59
|
+
concurrency: z.natural(),
|
|
60
|
+
})
|
|
61
|
+
.volatile(),
|
|
62
|
+
includeSubagents: z.boolean().volatile(),
|
|
63
|
+
auditFile: z.string().volatile(),
|
|
50
64
|
});
|
|
51
65
|
|
|
66
|
+
/**
|
|
67
|
+
* 从已解析的 Config 读取当前设置值(0.1.7:`config.<field>.get()`)。
|
|
68
|
+
* volatile 引用由 loader 就地更新,故每次裁决读取即为最新值。
|
|
69
|
+
* @param {object} config apply(ctx, config) 收到的已解析 Config
|
|
70
|
+
* @returns {object} 仅含 schema 字段的普通对象(未设字段为 undefined/空集合)
|
|
71
|
+
*/
|
|
72
|
+
export function readYoloConfig(config) {
|
|
73
|
+
return {
|
|
74
|
+
preset: config.preset.get(),
|
|
75
|
+
modes: config.modes.get(),
|
|
76
|
+
levels: config.levels.get(),
|
|
77
|
+
judge: config.judge.get(),
|
|
78
|
+
includeSubagents: config.includeSubagents.get(),
|
|
79
|
+
auditFile: config.auditFile.get(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
/**
|
|
53
84
|
* 剔除 schema 解析出的"空集合默认值"(schemastery 对 array/dict 缺省解析为
|
|
54
85
|
* []/{},标量缺省为 absent),避免把这些默认空值当成用户配置喂给
|
|
@@ -88,20 +119,17 @@ export function validateYoloSettings(value) {
|
|
|
88
119
|
}
|
|
89
120
|
|
|
90
121
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
122
|
+
* 注册 yolo-mode 的设置页面策略(0.1.7 `SettingsForms.configure`)。
|
|
123
|
+
*
|
|
124
|
+
* 插件自带 Web 客户端设置页(slot `settings.section`),故 `auto:false`
|
|
125
|
+
* 禁止宿主再自动生成重复页面;页面策略注册到本插件 fiber 的 effects,
|
|
126
|
+
* 卸载时自动注销。settings 服务缺失时 `ctx.inject` 回调不运行(零侵入,
|
|
127
|
+
* 插件继续按 Config schema 默认 + 条目 config 运行)。
|
|
128
|
+
*
|
|
129
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx 主插件 fiber 的上下文
|
|
97
130
|
*/
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
'[yolo-mode] no settings service mounted; using composition config and skipping settings section registration',
|
|
103
|
-
);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
settings.installSection(ctx, YOLO_SETTINGS_NAMESPACE, YoloSettingsSchema, entry, hooks);
|
|
131
|
+
export function installYoloSettingsPage(ctx) {
|
|
132
|
+
ctx.inject(['settings'], (child) => {
|
|
133
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
134
|
+
});
|
|
107
135
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-yolo-mode",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "dsh-yolo-mode —— DeepSeek Harness 双面包插件:当会话处于可写沙箱模式且审批策略为 ask 时,用大模型自动裁决沙箱升权申请,支持内置预设与自定义权限层级,并提供宿主 settings + 自发布设置桥(/yolo-mode)与 Web 客户端 UI。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -41,22 +41,22 @@
|
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"react": "^18.2.0",
|
|
43
43
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
44
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-timeout": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
44
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-timeout": "^0.1.7-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
|
47
47
|
"@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.2",
|
|
48
48
|
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
49
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
49
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.7-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
|
|
52
52
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"rolldown": "^1.2.4",
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
57
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-timeout": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
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",
|
|
60
60
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
@@ -74,14 +74,17 @@
|
|
|
74
74
|
"slots",
|
|
75
75
|
"locale",
|
|
76
76
|
"connection",
|
|
77
|
-
"remote"
|
|
77
|
+
"remote",
|
|
78
|
+
"remote.llm"
|
|
78
79
|
]
|
|
79
80
|
},
|
|
80
81
|
"compatibility": {
|
|
81
82
|
"dshReleases": {
|
|
82
83
|
"0.1.5-rc.1": "compatible",
|
|
83
84
|
"0.1.5-rc.2": "compatible",
|
|
84
|
-
"0.1.6-alpha.2": "compatible"
|
|
85
|
+
"0.1.6-alpha.2": "compatible",
|
|
86
|
+
"0.1.7-rc.1": "compatible",
|
|
87
|
+
"0.1.7-rc.2": "compatible"
|
|
85
88
|
},
|
|
86
89
|
"node": ">=20"
|
|
87
90
|
}
|