dsh-yolo-mode 0.4.0 → 0.5.0-beta.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,6 +2,47 @@
2
2
 
3
3
  本项目遵循 [Semantic Versioning](https://semver.org/)。
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ### 兼容:DSH 0.1.2-alpha.4
8
+
9
+ - **peerDependencies 升级**:`dsh-llm` / `dsh-timeout` / `dsh-settings` /
10
+ `dsh-client-connection` / `dsh-client-ui-slots` / `dsh-client-locale` →
11
+ `^0.1.2-alpha.4`;`dsh-client-runtime` / `dsh-host-apiproxy` →
12
+ `^0.1.1-rc.2`(alpha 期未重发,最高仅到 `0.1.1-rc.2`);`@deepseek-ai/schemastery`
13
+ 保持 `^3.18.1`。旧 `^0.1.0-rc.6` 预发布区间按 semver 预发布元组规则不会匹配
14
+ `0.1.2-alpha.4`,会把插件钉死在 rc.8。
15
+ - **settings 类服务迁移(lib/settings.js + lib/remote.js)**:alpha.4 删除
16
+ `installSettingsSection` / `settingsNamespace` 函数导出,`ctx.settings` 变为
17
+ `SettingsProvider` 类服务(默认导出)。命名空间改为纯 kebab-case 字符串字面量
18
+ `yolo-mode`(原品牌函数 `settingsNamespace('yolo-mode')` 的产物即同字符串);
19
+ 分区接线改为 `settings.installSection(ctx, ns, schema, entry, hooks)`,
20
+ 钩子(`setSource` / `onChange` / `validate`)语义不变。`SettingsConflictError`、
21
+ `settings.mutate` / `describe` / `writable` 均保留,桥接层仅去除品牌调用。
22
+ - **真实-Cordis 探针**:新增 `test/probe.test.mjs`,在真实
23
+ `@deepseek-ai/cordis` Context 上挂载插件本体(内存 `SettingsProvider` +
24
+ 假 llm + sandboxPolicy stub),覆盖:激活与命名空间注册、取消、透明委托
25
+ (非升权 / 沙箱模式门 / includeSubagents)、`yolo` 预设确定性放行、裁判
26
+ allow(真实 dsh-llm 组装)、settings 用户层更新即时生效。
27
+ - **devDependencies**:新增 `@deepseek-ai/cordis ^4.0.2`、`dsh-llm` /
28
+ `dsh-timeout` / `dsh-settings`(alpha.4)、`@deepseek-ai/schemastery`,
29
+ 供 `npm test`(`node --test`)直接运行。
30
+
31
+ ## [0.5.0] - 2026-08-28
32
+
33
+ ### 新增
34
+
35
+ - **决策表翻页**:状态弹窗的最近决策表改为分页展示(每页 5 条,倒序),新增「上一页 / 下一页」+ 页码指示(多页时显示);刷新导致列表长度跨页边界时自动回到第一页。
36
+ - **打开审计日志**:弹窗新增「打开日志」按钮,客户端经新 RPC 端点 `openLogFile` 请求宿主,用 OS 默认应用打开审计 JSONL(macOS `open` / Windows `cmd start` / Linux `xdg-open`);文件尚不存在返回 `log-not-found`,UI 显示友好提示(含解析出的日志路径)。
37
+ - **statusView 携带 auditFile**:生效审计日志路径随 statusView 返回(与主条目 `audit()` 同一解析规则),弹窗展示路径(按钮 title + 说明行)。
38
+ - **lib/audit.js 新模块**:审计日志路径解析(`resolveAuditFile` / `defaultAuditFile` / `auditFileExists`)与 OS 打开(`openFileWithDefaultApp` / `openerCommandFor`)抽为独立宿主模块;主条目 `audit()` 与桥接 `openLogFile` 端点共用同一解析规则,杜绝路径不一致。
39
+
40
+ ### 测试
41
+
42
+ - 新增 `test/audit.test.mjs`(路径解析 / 存在性 / 三平台打开命令)与 `test/state.test.mjs`(statusView 载荷装配含 auditFile)。
43
+ - `test/remote-bridge.test.mjs` 增 `openLogFile` 端点用例:文件存在 → 注入 openFile 收到解析路径;不存在 → `log-not-found` 且不调用 openFile;端点常量。
44
+ - `test/client.test.mjs` 增 `YoloStore.openLogFile` 转发用例与 Popup 分页渲染用例(20 条 → 5 条/页、翻页器、打开日志按钮与成功提示)。
45
+
5
46
  ## [0.4.0] - 2026-08-14
6
47
 
7
48
  ### 新增
package/README.md CHANGED
@@ -6,6 +6,8 @@
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/DeepSeek%20Harness-0.1.0--rc.6-blue)](https://github.com/deepseek-ai/deepseek-harness)
10
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
9
11
 
10
12
  ---
11
13
 
@@ -16,7 +18,7 @@
16
18
  - **自定义层级**:`levels` 支持逐目标模式、逐工具(`tools.<toolName>`)的 `allow / judge / delegate / deny` 策略,及 `error` / `unsure` 回退。
17
19
  - **每预设默认提示词**:`judge.systemPrompt` 留空时按预设自动选用对应裁判提示词(strict 最保守、permissive 宽松、custom 按层级表)。
18
20
  - **图形化配置**:设置面板「YOLO 审批」页在线修改预设 / 生效模式 / 裁判模型(下拉选择)/ 层级表,保存即生效;输入栏状态 chip + 决策统计面板。
19
- - **完整审计**:每次裁决落 JSONL 日志,含工具、目标模式、当前模式、理由、决策与结果。
21
+ - **完整审计**:每次裁决落 JSONL 日志,含工具、目标模式、当前模式、理由、决策与结果;状态弹窗一键「打开日志」用系统默认应用查看。
20
22
  - **fail-closed**:超时、非法输出、模型不可用、并发溢出等一切异常路径都回退为「拒绝」或「转人工」。
21
23
 
22
24
  ## 安装
@@ -31,26 +33,35 @@ dsh plugin --profile web add dsh-yolo-mode
31
33
  dsh plugin --profile web add <项目绝对路径>
32
34
  ```
33
35
 
34
- ### 2. 追加两行到 profile patch
36
+ ### 2.(可选)覆盖插件配置
35
37
 
36
- 编辑 `$DSH_HOME/profiles/web/cordis.patch.yml`,追加两个 `insert` 元素(主条目 + 设置桥接条目):
38
+ `dsh plugin add` 会通过插件包自带的 `cordis.patch.yml` 自动挂载 `yolo-mode` 与
39
+ `yolo-mode-bridge` 两个入口,**不需要**再手动 `insert`。需要调整配置时,在
40
+ `$DSH_HOME/profiles/web/cordis.patch.yml` 里按 id 覆盖主条目即可:
37
41
 
38
42
  ```yaml
39
- - insert:
40
- - id: yolo-mode
41
- name: dsh-yolo-mode
42
- config:
43
- preset: balanced
44
- judge:
45
- provider: <provider>
46
- model: <model>
47
- - insert:
48
- - id: yolo-mode-bridge
49
- name: dsh-yolo-mode/bridge
43
+ - id: yolo-mode
44
+ name: dsh-yolo-mode
45
+ config:
46
+ preset: balanced
47
+ judge:
48
+ provider: <provider>
49
+ model: <model>
50
50
  ```
51
51
 
52
+ > 注意:不要再用 `- insert:` 添加 `yolo-mode` / `yolo-mode-bridge`,否则启动会报
53
+ > `duplicate loader entry id: yolo-mode`。
54
+
52
55
  重启 DSH 并刷新浏览器后生效。
53
56
 
57
+ ### 安装注意事项
58
+
59
+ - 推荐直接 `dsh plugin --profile web add dsh-yolo-mode`(npm 发布版已包含构建产物,
60
+ peer 依赖由 DSH 的 `$DSH_HOME/profiles/node_modules` fallback 提供,开箱即用)。
61
+ - 本地 `link:` 开发时,checkout 需位于 `$DSH_HOME/profiles/` 下(或仓库自带
62
+ node_modules),否则 `@deepseek-ai/*` peer 依赖会因 Node 按真实路径解析而报
63
+ `ERR_MODULE_NOT_FOUND`。
64
+
54
65
  ## 配置
55
66
 
56
67
  插件行 `config` 全字段可选,未填按默认值:
@@ -107,7 +118,8 @@ levels:
107
118
 
108
119
  ## 界面
109
120
 
110
- - **输入栏 chip**:显示 `YOLO <preset>`,点击弹出统计面板(总审批 / 放行 / 拒绝 / 转人工 + 最近 20 条决策)。
121
+ - **输入栏 chip**:显示 `YOLO <preset>`,点击弹出统计面板(总审批 / 放行 / 拒绝 / 转人工 + 最近决策表,每页 5 条,带上一页 / 下一页翻页)。
122
+ - **打开日志**:弹窗右上「打开日志」按钮用系统默认应用打开审计 JSONL(`auditFile` 配置或默认 `%TEMP%/dsh-yolo/judge.log`);文件尚不存在时提示暂无记录。
111
123
  - **设置页**:「YOLO 审批」页在线修改预设、生效沙箱模式、裁判模型(provider / model 下拉,取自 Harness 模型配置)、层级表(JSON);切换预设时自动预填充该预设的默认提示词与层级表。
112
124
 
113
125
  ## 安全
@@ -0,0 +1,12 @@
1
+ # dsh-yolo-mode bundle patch.
2
+ #
3
+ # Adds the plugin's two entries: the main entry and the settings bridge that
4
+ # exposes the `yolo-mode` settings namespace to the Web UI.
5
+ # Install: dsh plugin --profile <name> add dsh-yolo-mode
6
+ # Restart `dsh --profile <name>` after installing.
7
+ - insert:
8
+ - id: yolo-mode
9
+ name: dsh-yolo-mode
10
+ - insert:
11
+ - id: yolo-mode-bridge
12
+ name: dsh-yolo-mode/bridge
package/lib/audit.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * dsh-yolo-mode —— 审计日志文件解析与打开(lib/audit.js)
3
+ *
4
+ * 审计 JSONL 路径的唯一解析来源:主条目(lib/index.js 的 audit())与桥接条目
5
+ * (openLogFile 端点)都必须落到同一个文件,否则「打开日志」会指向错误位置。
6
+ * 规则(design.md §8/§11.1,README 配置表 auditFile):
7
+ * - 配置显式给出非空 `auditFile` → 用配置值;
8
+ * - 否则回落默认 `%TEMP%/dsh-yolo/judge.log`(os.tmpdir(),宿主真实临时目录)。
9
+ *
10
+ * `openFileWithDefaultApp` 用 OS 默认应用打开文件(fire-and-forget):
11
+ * - darwin → `open <file>`
12
+ * - win32 → `cmd /c start "" "<file>"`(引号包实参,兼容含空格路径)
13
+ * - 其余 → `xdg-open <file>`
14
+ * 启动失败(如 xdg-open 缺失)经 child 'error' 事件返回 { ok:false },
15
+ * 绝不抛出(打开失败不致命,UI 只提示)。
16
+ *
17
+ * 纯函数层(resolveAuditFile / auditFileExists / openerCommandFor)无副作用,
18
+ * 可在普通 node 环境单测。
19
+ *
20
+ * @module lib/audit.js
21
+ */
22
+ import os from 'node:os';
23
+ import path from 'node:path';
24
+ import fs from 'node:fs';
25
+ import { spawn } from 'node:child_process';
26
+
27
+ /** 默认审计日志路径:`%TEMP%/dsh-yolo/judge.log`(design.md §8;每次调用取当前 tmpdir)。 */
28
+ export function defaultAuditFile() {
29
+ return path.join(os.tmpdir(), 'dsh-yolo', 'judge.log');
30
+ }
31
+
32
+ /**
33
+ * 解析生效的审计日志路径:显式非空 `auditFile` 优先(返回值去除首尾空白),
34
+ * 否则回落默认。
35
+ * @param {object|undefined} cfg 配置字形(view.value / effectiveConfig)
36
+ * @returns {string} 绝对或配置给定的审计日志路径
37
+ */
38
+ export function resolveAuditFile(cfg) {
39
+ const configured =
40
+ cfg && typeof cfg === 'object' && typeof cfg.auditFile === 'string' ? cfg.auditFile.trim() : '';
41
+ return configured !== '' ? configured : defaultAuditFile();
42
+ }
43
+
44
+ /**
45
+ * 审计日志文件是否已存在(尚无裁决时不创建、不弹错误对话框)。
46
+ * @param {string} file
47
+ * @returns {boolean}
48
+ */
49
+ export function auditFileExists(file) {
50
+ try {
51
+ return fs.existsSync(file);
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 按平台挑选「默认应用打开」命令(纯函数,可注入 platform 单测)。
59
+ * @param {string} file 目标文件路径
60
+ * @param {string} [platform] process.platform 值;缺省取当前进程
61
+ * @returns {{command: string, args: string[]}}
62
+ */
63
+ export function openerCommandFor(file, platform = process.platform) {
64
+ const target = String(file);
65
+ if (platform === 'darwin') return { command: 'open', args: [target] };
66
+ if (platform === 'win32') {
67
+ // cmd 的 start 把首个带引号实参当作窗口标题;用 "" 占位,文件实参整体包引号。
68
+ return { command: 'cmd', args: ['/c', 'start', '""', '"' + target.replace(/"/g, '\\"') + '"'] };
69
+ }
70
+ return { command: 'xdg-open', args: [target] };
71
+ }
72
+
73
+ /**
74
+ * 用 OS 默认应用打开文件(detached + unref 的 fire-and-forget)。
75
+ * @param {string} file
76
+ * @returns {Promise<{ok:true, value:{path:string}} | {ok:false, error:{code:string, message:string, details:object}}>}
77
+ */
78
+ export function openFileWithDefaultApp(file) {
79
+ return new Promise((resolve) => {
80
+ const { command, args } = openerCommandFor(file);
81
+ let settled = false;
82
+ const settle = (result) => {
83
+ if (settled) return;
84
+ settled = true;
85
+ resolve(result);
86
+ };
87
+ let child;
88
+ try {
89
+ child = spawn(command, args, { detached: true, stdio: 'ignore' });
90
+ } catch (err) {
91
+ settle({
92
+ ok: false,
93
+ error: {
94
+ code: 'open-failed',
95
+ message: 'failed to launch ' + command + ': ' + String(err && err.message ? err.message : err),
96
+ details: { path: file },
97
+ },
98
+ });
99
+ return;
100
+ }
101
+ child.once('error', (err) => {
102
+ settle({
103
+ ok: false,
104
+ error: {
105
+ code: 'open-failed',
106
+ message: 'failed to launch ' + command + ': ' + (err && err.message ? err.message : String(err)),
107
+ details: { path: file },
108
+ },
109
+ });
110
+ });
111
+ child.once('spawn', () => {
112
+ child.unref();
113
+ settle({ ok: true, value: { path: file } });
114
+ });
115
+ });
116
+ }
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let _deepseek_ai_dsh_client_web_react = require("@deepseek-ai/dsh-client-web-react");
9
8
  //#region src/client/locales.js
10
9
  /**
11
10
  * Bilingual dictionaries for the YOLO mode `settings.yoloMode` namespace.
@@ -55,6 +54,15 @@ window.__ModuleLoader__.load({
55
54
  statsDelegated: "Delegated",
56
55
  refresh: "Refresh",
57
56
  close: "Close",
57
+ openLog: "Open log",
58
+ openLogBusy: "Opening…",
59
+ openLogOk: "Log opened",
60
+ openLogFail: "Could not open log",
61
+ logNotFound: "The log file does not exist yet (no decisions recorded).",
62
+ logPath: "Log file",
63
+ pagePrev: "‹ Prev",
64
+ pageNext: "Next ›",
65
+ pageOf: "Page {current} / {total}",
58
66
  tableTime: "Time",
59
67
  tableTool: "Tool",
60
68
  tableTarget: "Target",
@@ -104,6 +112,15 @@ window.__ModuleLoader__.load({
104
112
  statsDelegated: "转人工",
105
113
  refresh: "刷新",
106
114
  close: "关闭",
115
+ openLog: "打开日志",
116
+ openLogBusy: "打开中…",
117
+ openLogOk: "日志已打开",
118
+ openLogFail: "无法打开日志",
119
+ logNotFound: "日志文件尚不存在(还没有裁决记录)。",
120
+ logPath: "日志文件",
121
+ pagePrev: "‹ 上一页",
122
+ pageNext: "下一页 ›",
123
+ pageOf: "第 {current} / {total} 页",
107
124
  tableTime: "时间",
108
125
  tableTool: "工具",
109
126
  tableTarget: "目标模式",
@@ -228,15 +245,16 @@ window.__ModuleLoader__.load({
228
245
  * invalidations (settings/document-updated, connection/reset) refresh the page.
229
246
  *
230
247
  * The store is a bare observable (implements subscribe/getSnapshot) so it can
231
- * be bound directly by `bindSnapshotSelector` (dsh-client-web-react); the
232
- * popup open/closed flag is also held here so the chip and the overlay share
233
- * one source of truth.
248
+ * be bound directly by `bindSnapshotSelector` (see ./bind.js); the popup
249
+ * open/closed flag is also held here so the chip and the overlay share one
250
+ * source of truth.
234
251
  */
235
252
  /** Channel + endpoints under the plugin's self-published bridge. */
236
253
  const YOLO_RPC_CHANNEL = "/yolo-mode";
237
254
  const YOLO_RPC_VIEW = "settingsView";
238
255
  const YOLO_RPC_STATUS = "statusView";
239
256
  const YOLO_RPC_MUTATE = "settingsMutate";
257
+ const YOLO_RPC_OPEN_LOG = "openLogFile";
240
258
  /** Initial snapshot returned by a freshly constructed store. */
241
259
  function initialYoloState() {
242
260
  return {
@@ -436,6 +454,15 @@ window.__ModuleLoader__.load({
436
454
  code
437
455
  };
438
456
  }
457
+ /**
458
+ * Ask the host to open the audit JSONL log file with the OS default
459
+ * application. The host resolves the effective path (view.value.auditFile or
460
+ * the tmp default) and returns { ok: true, value: { path } } on success, or
461
+ * { ok: false, error: { code: 'log-not-found' | 'open-failed', … } }.
462
+ */
463
+ async openLogFile() {
464
+ return this._call(YOLO_RPC_OPEN_LOG, {});
465
+ }
439
466
  /** Rebase the revision from a freshly loaded view without a full load. */
440
467
  adoptRevision(freshRevision) {
441
468
  this.set(adoptRevision({
@@ -1043,8 +1070,14 @@ window.__ModuleLoader__.load({
1043
1070
  /**
1044
1071
  * YOLO mode status popup (slot `shell.overlay`). Renders null while the store's
1045
1072
  * open flag is false; otherwise a stats card plus the recent decisions table
1046
- * (≤20, newest first) and a refresh button.
1073
+ * (paged, PAGE_SIZE per page, newest first), an "open audit log" button, and a
1074
+ * refresh button.
1075
+ *
1076
+ * Guard/hook split mirrors SettingsSection: the outer component validates the
1077
+ * slot inject face, the inner component owns all hooks unconditionally.
1047
1078
  */
1079
+ /** Rows per page of the recent-decisions table. */
1080
+ const PAGE_SIZE = 5;
1048
1081
  const popupStyle = {
1049
1082
  position: "absolute",
1050
1083
  top: 56,
@@ -1069,6 +1102,11 @@ window.__ModuleLoader__.load({
1069
1102
  alignItems: "center",
1070
1103
  justifyContent: "space-between"
1071
1104
  };
1105
+ const headerButtons = {
1106
+ display: "flex",
1107
+ alignItems: "center",
1108
+ gap: 8
1109
+ };
1072
1110
  const statGrid = {
1073
1111
  display: "grid",
1074
1112
  gridTemplateColumns: "repeat(4, 1fr)",
@@ -1123,6 +1161,36 @@ window.__ModuleLoader__.load({
1123
1161
  fontSize: 12,
1124
1162
  cursor: "pointer"
1125
1163
  };
1164
+ const secondaryButton = {
1165
+ backgroundColor: "#ffffff",
1166
+ color: "#374151",
1167
+ border: "1px solid #cbd5e1",
1168
+ borderRadius: 6,
1169
+ padding: "5px 12px",
1170
+ fontSize: 12,
1171
+ cursor: "pointer"
1172
+ };
1173
+ const pagerButton = {
1174
+ backgroundColor: "#ffffff",
1175
+ color: "#2563eb",
1176
+ border: "1px solid #cbd5e1",
1177
+ borderRadius: 6,
1178
+ padding: "3px 10px",
1179
+ fontSize: 12,
1180
+ cursor: "pointer"
1181
+ };
1182
+ const pagerButtonDisabled = {
1183
+ opacity: .4,
1184
+ cursor: "not-allowed"
1185
+ };
1186
+ const captionStyle = {
1187
+ margin: 0,
1188
+ color: "#6b7280",
1189
+ fontSize: 11,
1190
+ lineHeight: "16px"
1191
+ };
1192
+ const logResultOk = { color: "#16a34a" };
1193
+ const logResultErr = { color: "#dc2626" };
1126
1194
  /** Truncate long reason text for display. */
1127
1195
  function truncate(text, max) {
1128
1196
  if (typeof text !== "string") return "";
@@ -1136,19 +1204,82 @@ window.__ModuleLoader__.load({
1136
1204
  const useSnapshot = props.useSnapshot;
1137
1205
  const t = props.t;
1138
1206
  if (store === void 0 || useSnapshot === void 0 || t === void 0) return null;
1207
+ return ReactPopup({
1208
+ store,
1209
+ useSnapshot,
1210
+ t
1211
+ });
1212
+ }
1213
+ function ReactPopup({ store, useSnapshot, t }) {
1139
1214
  const state = useSnapshot((s) => s);
1215
+ const [page, setPage] = (0, react.useState)(0);
1216
+ const [logResult, setLogResult] = (0, react.useState)(void 0);
1217
+ const [logBusy, setLogBusy] = (0, react.useState)(false);
1218
+ const statusInfo = state.statusInfo && typeof state.statusInfo === "object" ? state.statusInfo : {};
1219
+ const recent = Array.isArray(statusInfo.recent) ? statusInfo.recent : [];
1220
+ const totalPages = Math.max(1, Math.ceil(recent.length / PAGE_SIZE));
1221
+ (0, react.useEffect)(() => {
1222
+ setPage(0);
1223
+ }, [totalPages]);
1224
+ (0, react.useEffect)(() => {
1225
+ if (!logResult || !logResult.ok) return;
1226
+ const timer = setTimeout(() => setLogResult(void 0), 4e3);
1227
+ return () => clearTimeout(timer);
1228
+ }, [logResult]);
1140
1229
  if (!state.open) return null;
1141
1230
  const info = state.statusInfo;
1142
1231
  const stats = info && info.stats ? info.stats : {};
1143
- const recent = info && Array.isArray(info.recent) ? info.recent.slice(0, 20) : [];
1144
- return (0, react.createElement)("div", { style: popupStyle }, (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("strong", {}, t("chip")), (0, react.createElement)("button", {
1232
+ const auditFile = typeof statusInfo.auditFile === "string" && statusInfo.auditFile !== "" ? statusInfo.auditFile : void 0;
1233
+ const safePage = Math.min(page, totalPages - 1);
1234
+ const pageRows = recent.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
1235
+ const openLog = async () => {
1236
+ setLogBusy(true);
1237
+ setLogResult(void 0);
1238
+ try {
1239
+ setLogResult(await store.openLogFile());
1240
+ } catch (err) {
1241
+ setLogResult({
1242
+ ok: false,
1243
+ error: {
1244
+ code: "internal",
1245
+ message: err && err.message ? String(err.message) : String(err)
1246
+ }
1247
+ });
1248
+ } finally {
1249
+ setLogBusy(false);
1250
+ }
1251
+ };
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", {
1253
+ style: logBusy ? Object.assign({}, secondaryButton, { opacity: .6 }) : secondaryButton,
1254
+ onClick: () => void openLog(),
1255
+ title: auditFile !== void 0 ? auditFile : void 0
1256
+ }, logBusy ? t("openLogBusy") : t("openLog")), (0, react.createElement)("button", {
1145
1257
  style: primaryButton,
1146
1258
  onClick: () => void store.load()
1147
- }, t("refresh"))), (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: {
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: {
1148
1260
  color: "#6b7280",
1149
1261
  fontSize: 12,
1150
1262
  margin: 0
1151
- } }, 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", {}, recent.map((row, idx) => tableRow(t, row, idx)))), (0, react.createElement)("div", { style: {
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: {
1152
1283
  display: "flex",
1153
1284
  justifyContent: "flex-end"
1154
1285
  } }, (0, react.createElement)("button", {
@@ -1156,6 +1287,13 @@ window.__ModuleLoader__.load({
1156
1287
  onClick: () => store.togglePopup()
1157
1288
  }, t("close"))));
1158
1289
  }
1290
+ /** User-facing message for a failed open-log attempt (by error code). */
1291
+ function logMessage(t, error) {
1292
+ const code = error && typeof error === "object" && typeof error.code === "string" ? error.code : void 0;
1293
+ const detail = error && typeof error === "object" && typeof error.message === "string" ? error.message : void 0;
1294
+ if (code === "log-not-found") return t("logNotFound") + (detail ? ": " + detail : "");
1295
+ return t("openLogFail") + (detail ? ": " + detail : "");
1296
+ }
1159
1297
  function statCellOf(t, key, value) {
1160
1298
  return (0, react.createElement)("div", { style: statCell }, (0, react.createElement)("div", { style: statValue }, value === void 0 || value === null ? "–" : String(value)), (0, react.createElement)("div", { style: statLabel }, t(key)));
1161
1299
  }
@@ -1172,6 +1310,33 @@ window.__ModuleLoader__.load({
1172
1310
  return "–";
1173
1311
  }
1174
1312
  //#endregion
1313
+ //#region src/client/bind.js
1314
+ /**
1315
+ * uSES bridge: turns any bare observable snapshot source into a selector hook.
1316
+ *
1317
+ * DSH rc8 removed the `@deepseek-ai/dsh-client-web-react` package (its client
1318
+ * store-binding helpers were folded into the core slot kit). This is a local
1319
+ * copy of the same `bindSnapshotSelector` contract, implemented against the
1320
+ * `react` seed provided by the DSH client module table, so the plugin keeps
1321
+ * working on rc8+ without an extra runtime dependency.
1322
+ */
1323
+ /**
1324
+ * Bind a bare observable source (subscribe/getSnapshot) to a selector hook.
1325
+ * The selector is applied to the snapshot on every render; identity stores can
1326
+ * simply pass `(s) => s`.
1327
+ *
1328
+ * @param store - snapshot source (must implement subscribe/getSnapshot).
1329
+ * @returns the selector hook.
1330
+ */
1331
+ function bindSnapshotSelector(store) {
1332
+ const subscribe = (listener) => store.subscribe(listener);
1333
+ const getSnapshot = () => store.getSnapshot();
1334
+ return function useSelector(selector) {
1335
+ const select = selector ?? ((state) => state);
1336
+ return (0, react.useSyncExternalStore)(subscribe, () => select(getSnapshot()));
1337
+ };
1338
+ }
1339
+ //#endregion
1175
1340
  //#region src/client/index.js
1176
1341
  /**
1177
1342
  * YOLO mode — browser half (DSH client plugin).
@@ -1211,7 +1376,7 @@ window.__ModuleLoader__.load({
1211
1376
  rpc: connection.rpc,
1212
1377
  llm: connection.api ? connection.api.llm : null
1213
1378
  });
1214
- const useSnapshot = (0, _deepseek_ai_dsh_client_web_react.bindSnapshotSelector)(store);
1379
+ const useSnapshot = bindSnapshotSelector(store);
1215
1380
  ctx.effect(() => {
1216
1381
  const refresh = (ns) => {
1217
1382
  if (ns !== void 0 && ns !== "yolo-mode") return;
package/lib/index.js CHANGED
@@ -18,7 +18,6 @@
18
18
  *
19
19
  * 纯 JavaScript(ESM),宿主代码仅 import node: 内置与同包 peer。
20
20
  */
21
- import os from 'node:os';
22
21
  import path from 'node:path';
23
22
  import fs from 'node:fs';
24
23
 
@@ -31,6 +30,7 @@ import {
31
30
  import { createJudge, defaultJudgePromptFor } from './judge.js';
32
31
  import { installYoloSettings, validateYoloSettings, pruneEmpty } from './settings.js';
33
32
  import { recordDecision, sessionOrigin } from './state.js';
33
+ import { resolveAuditFile } from './audit.js';
34
34
 
35
35
  export const name = 'dsh-yolo-mode';
36
36
 
@@ -105,10 +105,7 @@ export function apply(ctx, rawConfig) {
105
105
  return normalizeConfig(pruneEmpty(raw));
106
106
  }
107
107
 
108
- const auditFile = () => {
109
- const cfg = effectiveConfig();
110
- return cfg.auditFile ? cfg.auditFile : path.join(os.tmpdir(), 'dsh-yolo', 'judge.log');
111
- };
108
+ const auditFile = () => resolveAuditFile(effectiveConfig());
112
109
 
113
110
  let dirEnsured = false;
114
111
 
package/lib/remote.js CHANGED
@@ -16,21 +16,28 @@
16
16
  * - dispatch 抛错 → 500(headersSent 则 destroy)
17
17
  * 响应一律 `RpcResult` 信封({ rpcId, result: { ok, value|error } })。
18
18
  *
19
- * 三个端点:
19
+ * 四个端点:
20
20
  * settingsView → { writable, view }
21
21
  * settingsMutate → 应用带 expectedRevision 乐观锁的路径 op,返回新 redacted view;
22
22
  * 冲突 → settings-conflict(含 expected/actual),其余拒绝 → settings-rejected。
23
23
  * statusView → getStatusPayload()(本插件扩展;不触碰 settings)。
24
+ * openLogFile → 解析生效审计日志路径(view.value.auditFile || 默认 tmp 路径),
25
+ * 存在则用 OS 默认应用打开;不存在 → log-not-found。
26
+ * (本插件扩展;打开实现可注入,缺省 lib/audit.js openFileWithDefaultApp)
24
27
  *
28
+ /**
25
29
  * 纯映射 / 线信封辅助在顶部(无 cordis),可在普通 node 环境单测。
26
30
  *
31
+ * DSH 0.1.2-alpha.4 兼容:dsh-settings 的 `settingsNamespace()` 品牌函数已
32
+ * 删除,命名空间为纯 kebab-case 字符串字面量(`yolo-mode`);`mutate` /
33
+ * `describe` / `writable` 仍为 `SettingsProvider` 实例方法,
34
+ * `SettingsConflictError` 导出保留(code 'SETTINGS_CONFLICT')。
35
+ *
27
36
  * @module lib/remote.js
28
37
  */
29
- import {
30
- SettingsConflictError,
31
- settingsNamespace,
32
- } from '@deepseek-ai/dsh-settings';
38
+ import { SettingsConflictError } from '@deepseek-ai/dsh-settings';
33
39
  import { YOLO_SETTINGS_NAMESPACE } from './settings.js';
40
+ import { resolveAuditFile, auditFileExists, openFileWithDefaultApp } from './audit.js';
34
41
 
35
42
  /** 桥接通道绝对前缀路由(design.md §12.4)。 */
36
43
  export const YOLO_RPC_CHANNEL = '/yolo-mode';
@@ -40,6 +47,8 @@ export const YOLO_RPC_VIEW = 'settingsView';
40
47
  export const YOLO_RPC_MUTATE = 'settingsMutate';
41
48
  /** statusView 端点(本插件扩展)。 */
42
49
  export const YOLO_RPC_STATUS = 'statusView';
50
+ /** openLogFile 端点(本插件扩展):用 OS 默认应用打开审计 JSONL 日志。 */
51
+ export const YOLO_RPC_OPEN_LOG = 'openLogFile';
43
52
 
44
53
  /** 顶层校验失败时宿主用的固定 rpcId(镜像 invalidEnvelopeResponse)。 */
45
54
  export const INVALID_REQUEST_RPC_ID = 'invalid-request';
@@ -238,6 +247,8 @@ function yoloConflict(conflict) {
238
247
  /**
239
248
  * 对 settings seam 执行一次路径 op 变更并映射为 RpcResult(携带新 redacted view,
240
249
  * 或 settings-conflict / settings-rejected 错误)。纯函数可注入 mutate/describe 测试。
250
+ * alpha.4 起命名空间为纯 kebab-case 字符串(`settingsNamespace()` 品牌已删除),
251
+ * `ns` 原样透传给 SettingsProvider.mutate(其内部按 `^[a-z][a-z0-9-]*$` 校验)。
241
252
  * @param {Function} mutate settings.mutate 绑定
242
253
  * @param {Function} describe settings.describe 绑定
243
254
  * @param {string} ns 命名空间字符串
@@ -246,9 +257,8 @@ function yoloConflict(conflict) {
246
257
  * @returns {Promise<{ok:boolean, value?:object, error?:object}>}
247
258
  */
248
259
  export async function yoloMutate(mutate, describe, ns, ops, expectedRevision) {
249
- const branded = settingsNamespace(ns);
250
260
  try {
251
- await mutate(branded, ops, expectedRevision);
261
+ await mutate(ns, ops, expectedRevision);
252
262
  } catch (error) {
253
263
  if (error instanceof SettingsConflictError) {
254
264
  return { ok: false, error: yoloConflict(error) };
@@ -256,7 +266,7 @@ export async function yoloMutate(mutate, describe, ns, ops, expectedRevision) {
256
266
  return { ok: false, error: yoloRejected(ns, error) };
257
267
  }
258
268
  const descriptor = describe({ redactSecrets: true }).find(
259
- (candidate) => candidate.ns === branded,
269
+ (candidate) => candidate.ns === ns,
260
270
  );
261
271
  if (descriptor === undefined) {
262
272
  return {
@@ -333,15 +343,42 @@ function sendJson(res, status, payload) {
333
343
  res.end(JSON.stringify(payload));
334
344
  }
335
345
 
346
+ /**
347
+ * openLogFile 端点:从 settings 的 yolo-mode resolved view 解析生效审计日志路径
348
+ * (resolveAuditFile 与主条目 audit() 同一规则),文件不存在 → log-not-found;
349
+ * 存在则交给注入的 openFile(默认 OS 默认应用打开)。
350
+ * @param {object} settings settings 服务对象
351
+ * @param {(file: string) => Promise<{ok:boolean, value?:object, error?:object}>} openFile
352
+ * @returns {Promise<{ok:boolean, value?:object, error?:object}>}
353
+ */
354
+ async function openAuditLogEndpoint(settings, openFile) {
355
+ const view = pickYoloNamespaceView(settings.describe({ redactSecrets: true }));
356
+ const value = view && view.value && typeof view.value === 'object' ? view.value : {};
357
+ const file = resolveAuditFile(value);
358
+ if (!auditFileExists(file)) {
359
+ return {
360
+ ok: false,
361
+ error: {
362
+ code: 'log-not-found',
363
+ message: 'audit log file does not exist yet: ' + file,
364
+ details: { path: file },
365
+ },
366
+ };
367
+ }
368
+ return openFile(file);
369
+ }
370
+
336
371
  /**
337
372
  * 分派一个已校验端点。未知端点抛错(由调用方兜底为 500)。
338
373
  * @param {object} settings settings 服务对象
339
374
  * @param {() => object} getStatusPayload statusView 载荷装配(由桥接条目注入)
340
375
  * @param {string} endpoint
341
376
  * @param {unknown} payload
377
+ * @param {(file: string) => Promise<{ok:boolean, value?:object, error?:object}>} [openFile]
378
+ * openLogFile 端点的打开实现(测试注入 fake;缺省 OS 默认应用打开)
342
379
  * @returns {Promise<{ok:boolean, value?:unknown, error?:object}>}
343
380
  */
344
- async function dispatchYoloEndpoint(settings, getStatusPayload, endpoint, payload) {
381
+ async function dispatchYoloEndpoint(settings, getStatusPayload, endpoint, payload, openFile) {
345
382
  if (endpoint === YOLO_RPC_VIEW) {
346
383
  return {
347
384
  ok: true,
@@ -354,6 +391,9 @@ async function dispatchYoloEndpoint(settings, getStatusPayload, endpoint, payloa
354
391
  if (endpoint === YOLO_RPC_STATUS) {
355
392
  return { ok: true, value: getStatusPayload() };
356
393
  }
394
+ if (endpoint === YOLO_RPC_OPEN_LOG) {
395
+ return openAuditLogEndpoint(settings, openFile ?? openFileWithDefaultApp);
396
+ }
357
397
  if (endpoint === YOLO_RPC_MUTATE) {
358
398
  const request = payload ?? null;
359
399
  if (request === null || typeof request !== 'object' || request.ns !== String(YOLO_SETTINGS_NAMESPACE)) {
@@ -384,9 +424,11 @@ async function dispatchYoloEndpoint(settings, getStatusPayload, endpoint, payloa
384
424
  * @param {() => object} getStatusPayload statusView 载荷装配(由桥接条目注入)
385
425
  * @param {import('node:http').IncomingMessage} req
386
426
  * @param {import('node:http').ServerResponse} res
427
+ * @param {(file: string) => Promise<{ok:boolean, value?:object, error?:object}>} [openFile]
428
+ * openLogFile 端点的打开实现(测试注入 fake;缺省 OS 默认应用打开)
387
429
  * @returns {Promise<void>}
388
430
  */
389
- export async function handleYoloBridgeRequest(settings, getStatusPayload, req, res) {
431
+ export async function handleYoloBridgeRequest(settings, getStatusPayload, req, res, openFile) {
390
432
  const rawUrl = typeof req.url === 'string' ? req.url : '/';
391
433
  const hostHeader = req.headers.host;
392
434
 
@@ -448,7 +490,7 @@ export async function handleYoloBridgeRequest(settings, getStatusPayload, req, r
448
490
  return;
449
491
  }
450
492
  // 分派。
451
- const result = await dispatchYoloEndpoint(settings, getStatusPayload, endpoint, envelope.payload);
493
+ const result = await dispatchYoloEndpoint(settings, getStatusPayload, endpoint, envelope.payload, openFile);
452
494
  sendJson(res, 200, buildServerResponse(envelope.rpcId, result));
453
495
  }
454
496
 
package/lib/settings.js CHANGED
@@ -6,20 +6,24 @@
6
6
  * ./policy.js 的 `normalizeConfig`(fail-loud 抛错即拒绝写入)。
7
7
  *
8
8
  * 分层层(design.md §12.3):插件行 config 作为 settings 的 `base` 层,
9
- * 用户文档写在上层;`installYoloSettings` 走 `installSettingsSection` 的
10
- * 可选-settings 消费者接线,部署无 settings 服务时打 debug 日志跳过(零侵入)。
9
+ * 用户文档写在上层;`installYoloSettings` 走 `SettingsProvider.installSection`
10
+ * 的可选-settings 消费者接线,部署无 settings 服务时打 debug 日志跳过(零侵入)。
11
+ *
12
+ * DSH 0.1.2-alpha.4 兼容:dsh-settings 改为类服务——默认导出 `SettingsProvider`,
13
+ * `installSettingsSection` / `settingsNamespace` 函数导出已删除;命名空间改为
14
+ * 纯 kebab-case 字符串字面量(模板校验 `^[a-z][a-z0-9-]*$`),消费接线改为
15
+ * `ctx.settings.installSection(owner, ns, schema, entry, hooks)`。
11
16
  *
12
17
  * 纯 JavaScript(ESM),宿主代码仅 import node: 内置与同包 peer
13
18
  * (@deepseek-ai/dsh-settings、@deepseek-ai/schemastery)。
14
19
  *
15
20
  * @module lib/settings.js
16
21
  */
17
- import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
18
22
  import z from '@deepseek-ai/schemastery';
19
23
  import { normalizeConfig } from './policy.js';
20
24
 
21
25
  /** settings 命名空间:`yolo-mode`(与插件行 id 一致,纯小写 kebab-case)。 */
22
- export const YOLO_SETTINGS_NAMESPACE = settingsNamespace('yolo-mode');
26
+ export const YOLO_SETTINGS_NAMESPACE = 'yolo-mode';
23
27
 
24
28
  /** 预设枚举(与 ./policy.js PRSESTS 保持一致,供 union schema 使用)。 */
25
29
  const PRESETS = ['off', 'strict', 'balanced', 'permissive', 'yolo', 'custom'];
@@ -92,11 +96,12 @@ export function validateYoloSettings(value) {
92
96
  * { setSource, onChange, validate? }
93
97
  */
94
98
  export function installYoloSettings(ctx, entry, hooks) {
95
- if (ctx.get('settings') === undefined) {
99
+ const settings = ctx.get('settings');
100
+ if (settings === undefined) {
96
101
  ctx.logger.debug(
97
102
  '[yolo-mode] no settings service mounted; using composition config and skipping settings section registration',
98
103
  );
99
104
  return;
100
105
  }
101
- installSettingsSection(ctx, YOLO_SETTINGS_NAMESPACE, YoloSettingsSchema, entry, hooks);
106
+ settings.installSection(ctx, YOLO_SETTINGS_NAMESPACE, YoloSettingsSchema, entry, hooks);
102
107
  }
package/lib/state.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { PRESETS, defaultLevelsFor } from './policy.js'
17
17
  import { defaultJudgePromptFor } from './judge.js'
18
+ import { resolveAuditFile } from './audit.js'
18
19
 
19
20
  /** recent 环形缓冲上限(design.md §11.1/12.4,≤20)。 */
20
21
  export const RECENT_CAP = 20;
@@ -75,9 +76,9 @@ export function recordDecision(entry) {
75
76
 
76
77
  /**
77
78
  * 以浅拷贝装配 statusView 载荷(design.md §12.4)。
78
- * @param {object} cfg 含 { preset, judge?: { provider?, model? } } 的配置字形
79
+ * @param {object} cfg 含 { preset, judge?: { provider?, model? }, auditFile? } 的配置字形
79
80
  * (主条目传 effectiveConfig();桥接条目传 settings.describe 推导的 view 字形)
80
- * @returns {{preset:string, judgeConfigured:boolean, presetDefaults:object, stats:object, recent:Array<object>}}
81
+ * @returns {{preset:string, judgeConfigured:boolean, presetDefaults:object, stats:object, recent:Array<object>, auditFile:string}}
81
82
  */
82
83
  export function getStatusPayload(cfg) {
83
84
  const c = cfg && typeof cfg === 'object' ? cfg : {};
@@ -88,5 +89,8 @@ export function getStatusPayload(cfg) {
88
89
  presetDefaults,
89
90
  stats: { ...stats },
90
91
  recent: recent.map((r) => ({ ...r })),
92
+ // 生效的审计日志路径(resolveAuditFile 与主条目 audit() 同一解析规则),
93
+ // 供客户端「打开日志」按钮展示/复用。
94
+ auditFile: resolveAuditFile(c),
91
95
  };
92
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-yolo-mode",
3
- "version": "0.4.0",
3
+ "version": "0.5.0-beta.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",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "files": [
14
14
  "lib",
15
- "CHANGELOG.md"
15
+ "CHANGELOG.md",
16
+ "cordis.patch.yml"
16
17
  ],
17
18
  "repository": {
18
19
  "type": "git",
@@ -40,19 +41,23 @@
40
41
  "peerDependencies": {
41
42
  "react": "^18.2.0",
42
43
  "@deepseek-ai/cordis": "^4.0.1",
43
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
44
- "@deepseek-ai/dsh-timeout": "^0.1.0-rc.6",
45
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
46
- "@deepseek-ai/dsh-host-apiproxy": "^0.1.0-rc.6",
47
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
48
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
49
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
50
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
51
- "@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.6",
44
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.4",
45
+ "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.4",
46
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
47
+ "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.2",
48
+ "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
49
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.4",
50
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.4",
51
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.4",
52
52
  "@deepseek-ai/schemastery": "^3.18.1"
53
53
  },
54
54
  "devDependencies": {
55
- "rolldown": "^1.2.4"
55
+ "rolldown": "^1.2.4",
56
+ "@deepseek-ai/cordis": "^4.0.2",
57
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.4",
58
+ "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.4",
59
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
60
+ "@deepseek-ai/schemastery": "^3.18.1"
56
61
  },
57
62
  "scripts": {
58
63
  "build": "node scripts/build-client.mjs",
@@ -60,6 +65,9 @@
60
65
  "test": "node --test"
61
66
  },
62
67
  "dsh": {
68
+ "bundle": {
69
+ "patch": "./cordis.patch.yml"
70
+ },
63
71
  "client": {
64
72
  "platform": "web",
65
73
  "inject": [