dsh-dock 0.11.3 → 0.11.6
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/README.md +14 -1
- package/client.js +528 -194
- package/features/animation/host.js +5 -10
- package/features/animation/view.jsx +10 -10
- package/features/games/view.jsx +2 -2
- package/features/mobile-relay/host.js +68 -17
- package/features/mobile-relay/view.jsx +103 -23
- package/features/modelconfig/host.js +4 -4
- package/features/notify/host.js +5 -10
- package/features/notify/view.jsx +1 -1
- package/features/tokenlog/host.js +11 -10
- package/features/tokenlog/view.jsx +268 -3
- package/features/visionproxy/host.js +2 -3
- package/index.js +60 -29
- package/package.json +8 -6
- package/screenshots/dock-tokenlog-charts-2.png +0 -0
- package/screenshots/dock-tokenlog-charts-3.png +0 -0
- package/screenshots/dock-tokenlog-charts.png +0 -0
- package/src/client.jsx +16 -6
- package/src/host-core.js +148 -16
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/client.jsx
CHANGED
|
@@ -30,7 +30,7 @@ import { feature as fGames } from "../features/games/view.jsx";
|
|
|
30
30
|
import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
|
|
31
31
|
|
|
32
32
|
const name = "dsh-dock";
|
|
33
|
-
const DOCK_VERSION = "0.11.
|
|
33
|
+
const DOCK_VERSION = "0.11.6";
|
|
34
34
|
|
|
35
35
|
// ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
|
|
36
36
|
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation, fNotify, fRunState, fGames, fMobileRelay];
|
|
@@ -238,6 +238,11 @@ function fullDockCss() {
|
|
|
238
238
|
// 样式全局注入一次(入口按钮与右下角浮层挂在设置页之外,不能依赖页内 <style>)。
|
|
239
239
|
// 这里用 SHELL_CSS 唯一命名,并做防御:注入永不抛错、失败只降级不打断启动;
|
|
240
240
|
// 外部功能注册携带样式时可重算更新(外部样式后到)。
|
|
241
|
+
//
|
|
242
|
+
// ⚠️ 必须同时打 data-plugin="dsh-dock"(官方 claimStyles/removeOwnedStyles 归属协议):
|
|
243
|
+
// 只有 data-plugin-css 的无主 <style> 会被任意后 materialize 的插件 claim 走,
|
|
244
|
+
// 对方卸载时 removeOwnedStyles 会连带删掉功能坞样式——表现为「开关别的插件后样式坏了、
|
|
245
|
+
// 要手动刷新才正常」。见 packages/client/modules claimStyles。
|
|
241
246
|
function ensureCss() {
|
|
242
247
|
if (typeof document === "undefined") return;
|
|
243
248
|
cancelInitialCssSchedule();
|
|
@@ -247,19 +252,21 @@ function ensureCss() {
|
|
|
247
252
|
dockCssTag = document.querySelector('style[data-plugin-css="dsh-dock"]');
|
|
248
253
|
} else {
|
|
249
254
|
dockCssTag = document.createElement("style");
|
|
250
|
-
dockCssTag.dataset.pluginCss = "dsh-dock";
|
|
251
255
|
document.head.appendChild(dockCssTag);
|
|
252
256
|
}
|
|
253
257
|
}
|
|
258
|
+
// 归属与标识:缺 data-plugin 的旧标签/被 claim 走的标签一律改回本插件
|
|
259
|
+
dockCssTag.dataset.plugin = "dsh-dock";
|
|
260
|
+
dockCssTag.dataset.pluginCss = "dsh-dock";
|
|
254
261
|
dockCssTag.textContent = fullDockCss();
|
|
255
262
|
} catch (e) {
|
|
256
263
|
console.error("[dsh-dock] ensureCss failed:", e && e.message ? e.message : String(e));
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
|
-
//
|
|
260
|
-
//
|
|
266
|
+
// 首次注入延后到浏览器空闲期:功能坞样式很大,首帧同步解析会让整页短暂停顿。
|
|
267
|
+
// 之后的外部模块注册仍走 ensureCss() 立即更新;apply() 另有同步一击(见下)。
|
|
261
268
|
function scheduleInitialCss() {
|
|
262
|
-
if (typeof document === "undefined" || initialCssSchedule || (dockCssTag && dockCssTag.isConnected)) return;
|
|
269
|
+
if (typeof document === "undefined" || initialCssSchedule || (dockCssTag && dockCssTag.isConnected && dockCssTag.dataset.plugin === "dsh-dock")) return;
|
|
263
270
|
const run = () => {
|
|
264
271
|
initialCssSchedule = null;
|
|
265
272
|
ensureCss();
|
|
@@ -649,7 +656,10 @@ const ctxRef = { current: null };
|
|
|
649
656
|
|
|
650
657
|
export function apply(ctx) {
|
|
651
658
|
ctxRef.current = ctx;
|
|
652
|
-
|
|
659
|
+
// 同步注入归属完整的样式:插件开关/HMR 重进 apply 时若只靠 idle,
|
|
660
|
+
// 窗口期内其它插件 materialize 会 claim 走无主标签(或标签已被对方删掉)。
|
|
661
|
+
// 首屏成本可接受(textContent 写入),换「开合插件样式不坏」。
|
|
662
|
+
ensureCss();
|
|
653
663
|
initFeatureState(BUILTIN_FEATURES.concat(PLANNED_FEATURES));
|
|
654
664
|
const slots = ctx.get("slots");
|
|
655
665
|
if (slots === undefined) return;
|
package/src/host-core.js
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
// dsh-dock · 宿主共享内核:各功能模块(features/*/host.js)共用的常量与工具。
|
|
2
2
|
// 放在 src/ 而非 features/:不属于任何单个功能;提取功能模块独立成包时按需随包复制。
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { homedir } from 'node:os'
|
|
3
6
|
import z from '@deepseek-ai/schemastery'
|
|
4
7
|
|
|
5
|
-
/** dsh-dock 自有 settings
|
|
8
|
+
/** dsh-dock 自有 settings 命名空间 = profile 条目 id(cordis.patch.yml 的 id)。 */
|
|
6
9
|
export const DOCK_NS = 'dsh-dock'
|
|
7
10
|
|
|
8
11
|
/** 自有命名空间的 schema(功能开关 + 图片理解代理 + 任务动画/任务通知配置 + 远程访问账号)。
|
|
9
|
-
* 运行状态模块只读共享追踪、不写配置,因此没有自己的段。
|
|
12
|
+
* 运行状态模块只读共享追踪、不写配置,因此没有自己的段。
|
|
13
|
+
*
|
|
14
|
+
* dsh ≥ 0.1.7-alpha.1:settings 服务改为 SettingsForms——
|
|
15
|
+
* ① 插件必须导出同名 Config(否则 mutate 报 No configurable plugin entry);
|
|
16
|
+
* ② ns 是 profile 条目 id(此处 DOCK_NS=cordis.patch.yml 的 id);
|
|
17
|
+
* ③ 活编辑路径必须落在 .volatile() 字段上,否则 write 抛 not volatile。
|
|
18
|
+
* 每个顶层段整体 volatile:段内任意 path 可被面板 mutate,无需逐字段标记。 */
|
|
10
19
|
export const DockConfig = z.object({
|
|
11
20
|
// 宿主侧功能开关(id -> boolean):客户端面板 toggle 即时同步过来,
|
|
12
21
|
// 重启 dsh 后按此表决定宿主半部 setup 哪些功能(路由注册、事件订阅等)。
|
|
13
|
-
features: z.dict(z.boolean()).default({}),
|
|
22
|
+
features: z.dict(z.boolean()).default({}).volatile(),
|
|
14
23
|
remoteAuth: z.object({
|
|
15
24
|
/** 远程访问登录账号(明文用户名;密码只存加盐哈希)。 */
|
|
16
25
|
username: z.string().default(''),
|
|
17
26
|
passwordHash: z.string().default(''),
|
|
18
27
|
salt: z.string().default(''),
|
|
19
|
-
}).default({}),
|
|
28
|
+
}).default({}).volatile(),
|
|
20
29
|
visionProxy: z.object({
|
|
21
30
|
enabled: z.boolean().default(false),
|
|
22
31
|
provider: z.string().default(''),
|
|
23
32
|
model: z.string().default(''),
|
|
24
|
-
}).default({}),
|
|
33
|
+
}).default({}).volatile(),
|
|
25
34
|
// 【用量记录】模块的单价配置:官网抓取的刊例价常与实际计费不符(第三方/中转、
|
|
26
35
|
// 折扣、自建端点等),这里保存用户自填单价并优先用于费用估算。
|
|
27
36
|
// 详见 features/tokenlog/host.js 的 resolvePricing(优先级:自定义 > 官网 > 内置 > 兜底)。
|
|
@@ -59,7 +68,7 @@ export const DockConfig = z.object({
|
|
|
59
68
|
cacheRead: z.number().default(0.43),
|
|
60
69
|
cacheWrite: z.number().default(4.32),
|
|
61
70
|
}).default({}),
|
|
62
|
-
}).default({}),
|
|
71
|
+
}).default({}).volatile(),
|
|
63
72
|
// 【任务动画】模块:只放动画本身(动效开关/模式/桌面伙伴大小)。
|
|
64
73
|
// 通知相关的字段已全部迁往下面的 notify 段(见 migrateNotifyConfig);
|
|
65
74
|
// 旧值不在 schema 里也会被 schemastery 原样保留,迁移读得到。
|
|
@@ -68,7 +77,7 @@ export const DockConfig = z.object({
|
|
|
68
77
|
effectMode: z.string().default('flow'),
|
|
69
78
|
// 桌面伙伴场景缩放(0.85~2.2;浮层右下角也可直接拖动缩放)
|
|
70
79
|
robotScale: z.number().default(1.35),
|
|
71
|
-
}).default({}),
|
|
80
|
+
}).default({}).volatile(),
|
|
72
81
|
// 【任务通知】模块:页内卡片 / 提示音 / 浏览器系统通知 / 群机器人推送。
|
|
73
82
|
// 模块自身的启停由 features.notify 开关负责(独立菜单项),这里只放通知行为配置。
|
|
74
83
|
notify: z.object({
|
|
@@ -92,9 +101,99 @@ export const DockConfig = z.object({
|
|
|
92
101
|
feishuWebhook: z.string().default(''),
|
|
93
102
|
// 一次性迁移标记:animation 段的旧通知配置已搬到本段(搬完置 true,避免每次启动重复写)
|
|
94
103
|
migratedFromAnimation: z.boolean().default(false),
|
|
95
|
-
}).default({}),
|
|
104
|
+
}).default({}).volatile(),
|
|
96
105
|
})
|
|
97
106
|
|
|
107
|
+
// ── 本地配置镜像(替代已移除的 settings.get) ─────────────────────────────
|
|
108
|
+
// apply(ctx, config) 绑定带 volatile 引用的 Config;每次操作前 plainify 一次取快照。
|
|
109
|
+
// settings.mutate 成功后由调用方/ volatile-update 刷新。测试桩可用 seedDockConfig 注入。
|
|
110
|
+
|
|
111
|
+
/** @type {null | Record<string, unknown>} */
|
|
112
|
+
let dockConfigBound = null
|
|
113
|
+
|
|
114
|
+
/** 是否为 cosmokit Volatile 引用(跨副本用 Symbol 协议识别,不依赖包导入)。 */
|
|
115
|
+
function isVolatileRef(value) {
|
|
116
|
+
return typeof value === 'object' && value !== null && Symbol.for('cosmokit.volatile.write') in value
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 递归解开 volatile 引用 → 可 JSON 化的普通对象。 */
|
|
120
|
+
export function plainifyDockConfig(value) {
|
|
121
|
+
if (isVolatileRef(value)) return plainifyDockConfig(value.get())
|
|
122
|
+
if (Array.isArray(value)) return value.map(plainifyDockConfig)
|
|
123
|
+
if (value !== null && typeof value === 'object') {
|
|
124
|
+
const out = {}
|
|
125
|
+
for (const [k, v] of Object.entries(value)) out[k] = plainifyDockConfig(v)
|
|
126
|
+
return out
|
|
127
|
+
}
|
|
128
|
+
return value
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 由 index.js apply 挂载/刷新:持有 Config 引用并在 loader/volatile-update 后重读。 */
|
|
132
|
+
export function bindDockConfig(config) {
|
|
133
|
+
dockConfigBound = config === undefined || config === null ? null : plainifyDockConfig(config)
|
|
134
|
+
return dockConfigBound
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 测试/本地桩:直接写入镜像(绕过宿主 settings)。 */
|
|
138
|
+
export function seedDockConfig(value) {
|
|
139
|
+
dockConfigBound = value === undefined || value === null ? null : plainifyDockConfig(value)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 读自有命名空间根对象。优先级:
|
|
144
|
+
* 1) apply 绑定的 Config 镜像(新宿主活值)
|
|
145
|
+
* 2) 测试/旧宿主的 settings.get(若仍存在)
|
|
146
|
+
* 3) settings.describe 里 ns=DOCK_NS 的 value(SettingsForms 读路径)
|
|
147
|
+
* 4) null(调用方回退默认)
|
|
148
|
+
*/
|
|
149
|
+
export function readDockRoot(ctx) {
|
|
150
|
+
if (dockConfigBound) return dockConfigBound
|
|
151
|
+
try {
|
|
152
|
+
const settings = ctx && typeof ctx.get === 'function' ? ctx.get('settings') : null
|
|
153
|
+
if (settings) {
|
|
154
|
+
if (typeof settings.get === 'function') {
|
|
155
|
+
const v = settings.get(DOCK_NS)
|
|
156
|
+
if (v && typeof v === 'object') return v
|
|
157
|
+
}
|
|
158
|
+
if (typeof settings.describe === 'function') {
|
|
159
|
+
const views = settings.describe({ redactSecrets: false }) || []
|
|
160
|
+
const hit = views.find((d) => d && d.ns === DOCK_NS && d.value && typeof d.value === 'object')
|
|
161
|
+
if (hit) return hit.value
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch { /* settings 未挂载 */ }
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 写自有命名空间一段(走 SettingsForms.mutate;成功后同步本地镜像)。
|
|
170
|
+
* @param {object} ctx Cordis context
|
|
171
|
+
* @param {string[]} path 段路径(如 ['animation'])
|
|
172
|
+
* @param {unknown} value 整段值
|
|
173
|
+
* @param {number} [expectedRevision] describe 的 revision(乐观锁,可选)
|
|
174
|
+
*/
|
|
175
|
+
export async function mutateDockSection(ctx, path, value, expectedRevision) {
|
|
176
|
+
const settings = ctx && typeof ctx.get === 'function' ? ctx.get('settings') : null
|
|
177
|
+
if (!settings || typeof settings.mutate !== 'function') {
|
|
178
|
+
throw new Error('settings 服务不可用,配置无法持久化')
|
|
179
|
+
}
|
|
180
|
+
await settings.mutate(DOCK_NS, [{ op: 'set', path, value }], expectedRevision)
|
|
181
|
+
if (dockConfigBound) {
|
|
182
|
+
const next = { ...dockConfigBound }
|
|
183
|
+
let node = next
|
|
184
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
185
|
+
const key = path[i]
|
|
186
|
+
const child = node[key] && typeof node[key] === 'object' && !Array.isArray(node[key]) ? { ...node[key] } : {}
|
|
187
|
+
node[key] = child
|
|
188
|
+
node = child
|
|
189
|
+
}
|
|
190
|
+
if (path.length > 0) next[path[path.length - 1]] = value
|
|
191
|
+
else Object.assign(next, value)
|
|
192
|
+
dockConfigBound = next
|
|
193
|
+
}
|
|
194
|
+
return value
|
|
195
|
+
}
|
|
196
|
+
|
|
98
197
|
/** 通知配置字段清单(schema、迁移、客户端默认值三处共用同一份键名)。 */
|
|
99
198
|
export const NOTIFY_FIELDS = [
|
|
100
199
|
'notifyOnComplete', 'notifyOnError', 'notifyOnConfirm', 'notifyStayMs',
|
|
@@ -112,9 +211,7 @@ export const NOTIFY_FIELDS = [
|
|
|
112
211
|
*/
|
|
113
212
|
export async function migrateNotifyConfig(ctx) {
|
|
114
213
|
try {
|
|
115
|
-
const
|
|
116
|
-
if (!settings || typeof settings.get !== 'function' || typeof settings.mutate !== 'function') return
|
|
117
|
-
const root = settings.get(DOCK_NS)
|
|
214
|
+
const root = readDockRoot(ctx)
|
|
118
215
|
const notify = root && typeof root === 'object' && root.notify && typeof root.notify === 'object' ? root.notify : null
|
|
119
216
|
if (notify && notify.migratedFromAnimation === true) return
|
|
120
217
|
const animation = root && typeof root === 'object' && root.animation && typeof root.animation === 'object' ? root.animation : null
|
|
@@ -127,7 +224,7 @@ export async function migrateNotifyConfig(ctx) {
|
|
|
127
224
|
// 避免 settings 命名空间尚未生效时误把「空迁移」当完成(那会丢掉旧配置)。
|
|
128
225
|
if (copied === 0) return
|
|
129
226
|
const next = Object.assign({}, notify || {}, legacy, { migratedFromAnimation: true })
|
|
130
|
-
await
|
|
227
|
+
await mutateDockSection(ctx, ['notify'], next)
|
|
131
228
|
console.log('[dsh-dock] notify config migrated from animation section; fields copied:', copied)
|
|
132
229
|
} catch (e) {
|
|
133
230
|
console.warn('[dsh-dock] notify config migration skipped:', (e && e.message) || String(e))
|
|
@@ -144,20 +241,55 @@ export async function migrateNotifyConfig(ctx) {
|
|
|
144
241
|
*/
|
|
145
242
|
export async function migrateModelsFeatureId(ctx) {
|
|
146
243
|
try {
|
|
147
|
-
const
|
|
148
|
-
if (!settings || typeof settings.get !== 'function' || typeof settings.mutate !== 'function') return
|
|
149
|
-
const root = settings.get(DOCK_NS)
|
|
244
|
+
const root = readDockRoot(ctx)
|
|
150
245
|
const features = root && typeof root === 'object' && root.features && typeof root.features === 'object' ? root.features : null
|
|
151
246
|
if (!features || typeof features.models !== 'boolean' || typeof features.modelconfig === 'boolean') return
|
|
152
247
|
const next = Object.assign({}, features, { modelconfig: features.models })
|
|
153
248
|
delete next.models
|
|
154
|
-
await
|
|
249
|
+
await mutateDockSection(ctx, ['features'], next)
|
|
155
250
|
console.log('[dsh-dock] feature id migrated: features.models -> features.modelconfig =', features.models)
|
|
156
251
|
} catch (e) {
|
|
157
252
|
console.warn('[dsh-dock] models feature id migration skipped:', (e && e.message) || String(e))
|
|
158
253
|
}
|
|
159
254
|
}
|
|
160
255
|
|
|
256
|
+
/**
|
|
257
|
+
* 一次性迁移:旧 settings.yaml 已被宿主改名为 settings.yaml.imported;
|
|
258
|
+
* 当时 dsh-dock 尚未导出 Config,importLegacyDocument 对本段 update 会失败,
|
|
259
|
+
* 功能开关表停在 imported 文件里。配置可写后把 features 补写进 profile。
|
|
260
|
+
*/
|
|
261
|
+
export async function migrateImportedFeatures(ctx) {
|
|
262
|
+
try {
|
|
263
|
+
const root = readDockRoot(ctx)
|
|
264
|
+
const features = root && typeof root === 'object' && root.features && typeof root.features === 'object' ? root.features : null
|
|
265
|
+
if (features && Object.keys(features).length > 0) return
|
|
266
|
+
const home = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
267
|
+
const file = join(home, 'settings.yaml.imported')
|
|
268
|
+
if (!existsSync(file)) return
|
|
269
|
+
const text = readFileSync(file, 'utf8')
|
|
270
|
+
// 极简 YAML:仅取 dsh-dock.features 下的 `key: true|false` 行(避免引入完整解析器)
|
|
271
|
+
const lines = text.split(/\r?\n/)
|
|
272
|
+
let inDock = false
|
|
273
|
+
let inFeatures = false
|
|
274
|
+
const imported = {}
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
if (/^dsh-dock:\s*$/.test(line)) { inDock = true; inFeatures = false; continue }
|
|
277
|
+
if (inDock && /^\S/.test(line) && !/^dsh-dock:/.test(line)) { inDock = false; inFeatures = false }
|
|
278
|
+
if (inDock && /^\s{2}features:\s*$/.test(line)) { inFeatures = true; continue }
|
|
279
|
+
if (inDock && inFeatures && /^\s{2}\S/.test(line)) { inFeatures = false }
|
|
280
|
+
if (inDock && inFeatures) {
|
|
281
|
+
const m = /^\s{4}([A-Za-z0-9_-]+):\s*(true|false)\s*$/.exec(line)
|
|
282
|
+
if (m) imported[m[1]] = m[2] === 'true'
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (Object.keys(imported).length === 0) return
|
|
286
|
+
await mutateDockSection(ctx, ['features'], imported)
|
|
287
|
+
console.log('[dsh-dock] features imported from settings.yaml.imported:', imported)
|
|
288
|
+
} catch (e) {
|
|
289
|
+
console.warn('[dsh-dock] imported features migration skipped:', (e && e.message) || String(e))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
161
293
|
/** 任务动画 effectMode 合法值(客户端动画模式)。 */
|
|
162
294
|
export const ANIMATION_MODES = [
|
|
163
295
|
'flow', 'breathe', 'ring', 'orbit', 'robot', 'matrix', 'stars', 'aurora', 'space',
|