dsh-dock 0.20.0 → 0.21.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.
@@ -1413,11 +1413,16 @@ export function TokenLogChip(props) {
1413
1413
  const gone = isTokenlogRouteGone();
1414
1414
  // chip 文案用紧凑人民币金额(2 位小数),避免撑爆输入卡工具行;
1415
1415
  // 完整金额在 title 悬浮提示里。
1416
- const label = gone
1416
+ // 手机上输入区工具行很窄:主文案(图标 + token 数)单独一段;费用放进 .dockchip-x,
1417
+ // 窄屏由 CSS 隐藏,避免整条被截成「⛁ 2…」。完整信息仍在 title 悬浮提示里。
1418
+ const hasData = !!t && !gone && !(snap.err && !t);
1419
+ const labelIcon = hasData ? "⛁ " : "";
1420
+ const labelMain = gone
1417
1421
  ? "用量·未启用"
1418
1422
  : snap.err && !t
1419
1423
  ? (snap.err.includes("重启") || snap.err.includes("启用") ? "用量·未启用" : "用量·失败")
1420
- : (t ? "⛁ " + fmtCompact(t.totalTokens) + (t.cost > 0 ? " · " + fmtCostCny(t.cost, snap.rate) : "") : "⛁ …");
1424
+ : (t ? fmtCompact(t.totalTokens) : "…");
1425
+ const labelExtra = hasData && t.cost > 0 ? " · " + fmtCostCny(t.cost, snap.rate) : "";
1421
1426
  const title = gone
1422
1427
  ? routeGoneMessage() + "\n(可在功能坞里启用「用量记录」)"
1423
1428
  : t
@@ -1428,7 +1433,7 @@ export function TokenLogChip(props) {
1428
1433
  <button type="button" className={"dockchip" + (snap.err && !t && !gone ? " err" : "")} title={title} aria-label="会话用量"
1429
1434
  onClick={() => openPanel("tokenlog", { sessionId: sid, navAt: Date.now() })}>
1430
1435
  <span className="dockchip-dot" style={{ background: gone ? "var(--dsw-alias-label-tertiary,#8b95ab)" : "var(--dk-warn)" }} />
1431
- <span>{label}</span>
1436
+ {labelIcon ? <span className="dockchip-ic">{labelIcon}</span> : null}<span>{labelMain}</span>{labelExtra ? <span className="dockchip-x">{labelExtra}</span> : null}
1432
1437
  </button>
1433
1438
  );
1434
1439
  }
package/index.js CHANGED
@@ -19,6 +19,7 @@
19
19
  // - modelconfig 模型设置:模型目录的输入模态与思考强度(写回官方配置)
20
20
  // - providerconfig 模型提供商:Provider 增删改 + API Key + 拉取上游模型列表
21
21
  // - mobile-relay 远程访问:账号密码登录的远程入口(网关 + 账号 + 浏览模式目录选择器)
22
+ // - mobile 手机适配:手机/窄屏体验优化(客户端运行时注入,关闭后零残留;宿主半部只留开关占位)
22
23
  import { DockConfig, sendJson, readBody, rejectUntrustedRequest, migrateNotifyConfig, migrateModelsFeatureId, migrateImportedFeatures, migrateImportedSections, bindDockConfig, readDockRoot, mutateDockSection } from './src/host-core.js'
23
24
  import { injectWelcomeAck } from './src/welcome-ack.js'
24
25
  import { feature as fModels } from './features/modelconfig/host.js'
@@ -31,6 +32,7 @@ import { feature as fNotify } from './features/notify/host.js'
31
32
  import { feature as fRunState } from './features/runstate/host.js'
32
33
  import { feature as fTts } from './features/tts/host.js'
33
34
  import { feature as fMobileRelay } from './features/mobile-relay/host.js'
35
+ import { feature as fMobile } from './features/mobile/host.js'
34
36
 
35
37
  export const name = 'dsh-dock'
36
38
 
@@ -74,6 +76,7 @@ export function apply(ctx, config) {
74
76
  fRunState,
75
77
  fTts,
76
78
  fMobileRelay,
79
+ fMobile,
77
80
  ]
78
81
 
79
82
  // 绑定 Config 镜像(替代已移除的 settings.get);volatile 字段热更时重绑。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-dock",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计,支持各模型自定义单价、多段分时价与周末/节假日谷价)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D 场景)、任务通知(完成/异常/需确认通知、提示音、语音播报可选音色、系统通知、钉钉/飞书推送)、运行状态(进行中任务与最近完成一览)、语音朗读(浏览器 TTS 读回复:回合下方喇叭按钮 + 自动朗读);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -43,7 +43,7 @@
43
43
  "scripts": {
44
44
  "build:client": "node scripts/build-client.mjs",
45
45
  "test:client": "node scripts/test-client-views.mjs",
46
- "test:host": "node scripts/test-task-host.mjs && node scripts/test-tokenlog-host.mjs && node scripts/test-balance-host.mjs && node scripts/test-route-trust-host.mjs && node scripts/test-tts-host.mjs && node scripts/test-legacy-import.mjs && node scripts/test-mobile-relay-host.mjs && node scripts/test-mobile-relay.mjs && node scripts/test-welcome-ack.mjs",
46
+ "test:host": "node scripts/test-task-host.mjs && node scripts/test-tokenlog-host.mjs && node scripts/test-balance-host.mjs && node scripts/test-route-trust-host.mjs && node scripts/test-tts-host.mjs && node scripts/test-legacy-import.mjs && node scripts/test-mobile-relay-host.mjs && node scripts/test-mobile-relay.mjs && node scripts/test-mobile-host.mjs && node scripts/test-welcome-ack.mjs",
47
47
  "dev:client": "node scripts/watch-client.mjs",
48
48
  "dev:link": "node scripts/dev-link-deps.mjs"
49
49
  },
Binary file
package/src/client.jsx CHANGED
@@ -30,12 +30,13 @@ import { feature as fRunState } from "../features/runstate/view.jsx";
30
30
  import { feature as fTts } from "../features/tts/view.jsx";
31
31
  import { feature as fGames } from "../features/games/view.jsx";
32
32
  import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
33
+ import { feature as fMobile } from "../features/mobile/view.js";
33
34
 
34
35
  const name = "dsh-dock";
35
- const DOCK_VERSION = "0.20.0";
36
+ const DOCK_VERSION = "0.21.0";
36
37
 
37
38
  // ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
38
- const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fProviderConfig, fHeartbeat, fTheme, fBalance, fAnimation, fNotify, fRunState, fTts, fGames, fMobileRelay];
39
+ const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fProviderConfig, fHeartbeat, fTheme, fBalance, fAnimation, fNotify, fRunState, fTts, fGames, fMobileRelay, fMobile];
39
40
  // 规划占位(路线图):接入后移除并建 features/<id>/ 模块
40
41
  const PLANNED_FEATURES = [];
41
42
  const PLANNED_NOTES = {};
@@ -163,6 +164,12 @@ const SHELL_CSS = [
163
164
  ".dockm-name{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;}",
164
165
  ".dockm-desc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
165
166
  ".dockm-view{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
167
+ ".dockm-page{box-sizing:border-box;height:100%;overflow:auto;padding:20px 24px 32px;}",
168
+ ".dockm-page-head{display:flex;align-items:flex-start;gap:10px;margin-bottom:16px;}",
169
+ ".dockm-page-icon{flex:none;display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);}",
170
+ ".dockm-page-titles{min-width:0;}",
171
+ ".dockm-page-title{font-size:17px;font-weight:600;color:var(--dsw-alias-label-primary);line-height:1.3;}",
172
+ ".dockm-page-sub{font-size:12px;color:var(--dsw-alias-label-tertiary);margin-top:2px;}",
166
173
  ".dockm-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
167
174
  ".dockm-err{color:var(--dsw-alias-state-error-primary);}",
168
175
  ".dockm-foot{margin-top:auto;display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;flex-wrap:wrap;}",
@@ -194,7 +201,11 @@ const SHELL_CSS = [
194
201
  ".dockm-foot-swlabel.on{color:var(--dsw-alias-state-success-primary);}",
195
202
  // 窄屏:功能坞是一级工作台而不是被压扁的桌面弹窗。全屏承接安全区,导航改横向,
196
203
  // 禁用桌面拖拽/缩放,所有导航和关闭动作保持至少 44px 的可点击面积。
197
- "@media (max-width:680px){.dockm-backdrop{align-items:stretch;justify-content:stretch;background:var(--dsw-alias-bg-layer-2);backdrop-filter:none}.dockm-dialog,.dockm-dialog.dockm-max{position:fixed!important;left:0!important;top:0!important;right:auto!important;bottom:auto!important;width:100dvw!important;height:100dvh!important;min-height:100dvh;border:0;border-radius:0;box-shadow:none}.dockm-dialog.dockm-min .dockm-body{display:flex}.dockm-head{min-height:60px;box-sizing:border-box;padding:calc(env(safe-area-inset-top) + 8px) 12px 8px;cursor:default;touch-action:manipulation}.dockm-sub,.dockm-win,.dockm-resize{display:none}.dockm-title{font-size:16px}.dockm-close{width:44px;height:44px;font-size:17px}.dockm-body{flex-direction:column;min-height:0}.dockm-nav{box-sizing:border-box;width:auto;max-width:100%;min-height:56px;flex-direction:row;gap:6px;padding:6px 10px;overflow-x:auto;overflow-y:hidden;border-right:0;border-bottom:1px solid var(--dsw-alias-border-l1);scrollbar-width:none;overscroll-behavior-x:contain}.dockm-nav::-webkit-scrollbar{display:none}.dockm-nav-item{flex:none;min-height:44px;padding:0 12px;font-size:13px;touch-action:manipulation}.dockm-nav-item .dockm-badge{display:none}.dockm-content{padding:16px max(16px,env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));gap:14px;overscroll-behavior:contain}.dockm-content-head{gap:5px}.dockm-name{font-size:16px}.dockm-desc{font-size:13px;line-height:1.55}.dockm-foot{padding-top:12px;font-size:11px}.dockm-foot-sw{width:100%;margin-left:0;justify-content:space-between}.dock-sw{width:44px;height:26px}.dock-sw::after{top:3px;left:3px;width:18px;height:18px}.dock-sw.on::after{transform:translateX(18px)}.dockh-grid{grid-template-columns:1fr;gap:10px}.dockh-card{padding:12px}.dockh-desc{font-size:13px}.dockh-go{font-size:12px}}",
204
+ "@media (max-width:680px){.dockm-backdrop{align-items:stretch;justify-content:stretch;background:var(--dsw-alias-bg-layer-2);backdrop-filter:none}.dockm-dialog,.dockm-dialog.dockm-max{position:fixed!important;left:0!important;top:0!important;right:auto!important;bottom:auto!important;width:100dvw!important;height:100dvh!important;min-height:100dvh;border:0;border-radius:0;box-shadow:none}.dockm-dialog.dockm-min .dockm-body{display:flex}.dockm-head{min-height:60px;box-sizing:border-box;padding:calc(env(safe-area-inset-top) + 8px) 12px 8px;cursor:default;touch-action:manipulation}.dockm-sub,.dockm-win,.dockm-resize{display:none}.dockm-title{font-size:16px}.dockm-close{width:44px;height:44px;font-size:17px}.dockm-body{flex-direction:column;min-height:0}.dockm-nav{box-sizing:border-box;width:auto;max-width:100%;min-height:56px;flex-direction:row;gap:6px;padding:6px 10px;overflow-x:auto;overflow-y:hidden;border-right:0;border-bottom:1px solid var(--dsw-alias-border-l1);scrollbar-width:none;overscroll-behavior-x:contain}.dockm-nav::-webkit-scrollbar{display:none}.dockm-nav-item{flex:none;min-height:44px;padding:0 12px;font-size:13px;touch-action:manipulation}.dockm-nav-item .dockm-badge{display:none}.dockm-content{padding:16px max(16px,env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));gap:14px;overscroll-behavior:contain}.dockm-content-head{gap:5px}.dockm-name{font-size:16px}.dockm-desc{font-size:13px;line-height:1.55}.dockm-foot{padding-top:12px;font-size:11px}.dockm-foot-sw{width:100%;margin-left:0;justify-content:space-between}.dock-sw{width:44px;height:26px}.dock-sw::after{top:3px;left:3px;width:18px;height:18px}.dock-sw.on::after{transform:translateX(18px)}.dockh-grid{grid-template-columns:minmax(0,1fr);gap:10px;min-width:0}.dockh-card{padding:12px;box-sizing:border-box;min-width:0}.dockh-desc{font-size:13px}.dockh-go{font-size:12px}}",
205
+ /* 手机端:功能坞弹层挂在 .overlayLayer(z20) 内,会被 z71 的侧栏抽屉盖住(只剩右侧一条)。\n 把 overlayLayer 在窄屏抬到抽屉之上,面板才能全屏覆盖——且不依赖【手机适配】模块是否启用。 */
206
+ "@media (max-width:767px){[class*=\"overlayLayer\"]{z-index:100 !important}}",
207
+ /* 手机:输入卡里的「轮/步/token/缓存」状态条移到输入框上方,压成一行不溢出;空会话时把输入卡贴底 */
208
+ "@media (max-width:767px){[class*=\"scrollBody\"]>[class*=\"composerSeat\"]{margin-top:auto!important;padding-bottom:0!important}[class*=\"composerSeat\"] [class*=\"_dock\"]{order:-1!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important;padding:0 2px 6px!important;gap:6px!important;font-size:10px!important;overflow:hidden!important}[class*=\"composerSeat\"] [class*=\"_dock\"]>*{min-width:0}}",
198
209
  "@media (prefers-reduced-motion:reduce){.dockm-backdrop,.dockm-dialog{animation:none}.dockm-nav-item,.dockm-close,.dockm-win,.dock-sw{transition:none}}",
199
210
  ".dockh-desc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;}",
200
211
  ".dockh-stat{color:var(--dsw-alias-label-tertiary);font-size:12px;border-top:1px solid var(--dsw-alias-border-l1);padding-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
@@ -220,7 +231,9 @@ const SHELL_CSS = [
220
231
  ".dockchip > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}",
221
232
  ".dockchip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
222
233
  ".dockchip .dockchip-dot{width:6px;height:6px;border-radius:50%;flex:none;}",
223
- ".dockchip.err{color:var(--dsw-alias-state-error-primary);}"
234
+ ".dockchip.err{color:var(--dsw-alias-state-error-primary);}",
235
+ /* 手机:输入区工具行很窄,给 chips 稍多宽度,并隐藏费用等次要后缀(.dockchip-x),避免被截成「⛁ 2…/余…」 */
236
+ "@media (max-width:767px){.dockchip-row{max-width:min(216px,40cqw)}.dockchip-x{display:none}.dockchip{font-size:10px!important;padding:1px 2px!important;gap:3px!important}.dockchip .dockchip-dot{width:4px;height:4px}.dockchip-ic{display:none}.dockchip-pre{display:none}}"
224
237
  ].join("\n");
225
238
 
226
239
  let dockCssTag = null;
@@ -616,6 +629,19 @@ function DockPanel(props) {
616
629
  }));
617
630
  }
618
631
 
632
+ // ---- 功能坞主面板页(sidebar.panellist 一级入口 → main 面板) ----
633
+ // 作为侧栏面板入口注册后,DSH 自带的入口自定义(Pin/更多)即可让用户把它固定在侧栏顶部
634
+ // (“放到上面”),或取消固定收进侧栏「更多」菜单(“放到更多里”)。
635
+ function DockPage() {
636
+ return react.createElement("div", { className: "dockm-page" },
637
+ react.createElement("div", { className: "dockm-page-head" },
638
+ react.createElement("span", { className: "dockm-page-icon" }, react.createElement(DockIcon, { size: 20 })),
639
+ react.createElement("div", { className: "dockm-page-titles" },
640
+ react.createElement("div", { className: "dockm-page-title" }, "功能坞"),
641
+ react.createElement("div", { className: "dockm-page-sub" }, "所有小功能集中在这里管理。侧栏图标可在「更多」菜单里 Pin 到顶部,或取消固定收进更多。"))),
642
+ react.createElement("div", { className: "dockm-page-body" }, react.createElement(DockPanel, { params: null })));
643
+ }
644
+
619
645
  // ---- 会话输入区工具行 chips:已启用功能的随身小控件(模型选择器左侧) ----
620
646
  // 功能模块在 feature 描述符上挂可选 Chip 组件(props: { ctx, session, sessionId, input, feature }),
621
647
  // 启用即在 conversation.input.left 渲染;点击通常经 openPanel(...) 打开功能坞定位到对应功能页。
@@ -683,9 +709,14 @@ export function apply(ctx) {
683
709
  const slots = ctx.get("slots");
684
710
  if (slots === undefined) return;
685
711
 
686
- slots.inject("sidebar.footer.action", () => slots.register(
687
- { name: "sidebar.footer.action", id: "dsh-dock", order: 1, label: "功能坞" },
688
- (props) => react.createElement(DockEntry, props)));
712
+ // 侧栏一级入口(sidebar.panellist)+ 对应主面板(main):注册成面板入口后,DSH 自带的
713
+ // 入口自定义可以「固定到左侧栏」= 显示在顶部图标区;取消固定 = 收进侧栏「更多」菜单。
714
+ slots.inject("sidebar.panellist", () => slots.register(
715
+ { name: "sidebar.panellist", id: "dsh-dock", order: 30, label: "功能坞" },
716
+ (props) => react.createElement(DockIcon, { size: (props && props.size) || 18 })));
717
+ slots.inject("main", () => slots.register(
718
+ { name: "main", key: "dsh-dock", label: "功能坞" },
719
+ () => react.createElement(DockPage, null)));
689
720
  slots.inject("shell.overlay", () => slots.register(
690
721
  { name: "shell.overlay", id: "dsh-dock-panel", order: 21, label: "功能坞面板" },
691
722
  () => react.createElement(DockModal, null)));
package/src/host-core.js CHANGED
@@ -142,6 +142,12 @@ export const DockConfig = z.object({
142
142
  voiceVolume: z.number().default(1),
143
143
  voiceLang: z.string().default(''),
144
144
  voiceURI: z.string().default(''),
145
+ // 事件 × 渠道开关矩阵:扁平键形如 card:complete / sound:error / system:confirm /
146
+ // voice:stuck / dingtalk:complete / feishu:error。未设置的键由客户端回退到旧的
147
+ // notifyOn* / voiceOn* 字段(升级不改变既有行为)。
148
+ eventMatrix: z.dict(z.boolean()).default({}),
149
+ // 群机器人消息口径三挡:brief / medium / detailed(宿主发消息时按此裁剪正文)。
150
+ pushMode: z.string().default('detailed'),
145
151
  // 一次性迁移标记:animation 段的旧通知配置已搬到本段(搬完置 true,避免每次启动重复写)
146
152
  migratedFromAnimation: z.boolean().default(false),
147
153
  }).default({}).volatile(),