dsh-dfy-pet 2.6.1 → 2.9.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/README.md CHANGED
@@ -11,13 +11,92 @@
11
11
  - **会自己冒泡。** 没有其他交互时,每 9–16 秒挥手一次。
12
12
  - **点击 / 轻触 / <kbd>Enter</kbd> / <kbd>Space</kbd>:** 她蹦一下(或挥手),
13
13
  飘起三颗爱心,并随机说一句六句本地化短句之一。
14
+ - **双击(或聚焦后按 <kbd>Enter</kbd>/<kbd>Space</kbd>):** 在她旁边弹出对话面板,
15
+ 输入框可直接打字,见下方「双击对话」一节。
14
16
  - **拖动:** 拎起来放到任意位置;位置记在 `localStorage` 里,窗口尺寸变化时会重新
15
17
  夹取边界,保证她不会跑到屏幕外找不回来。
16
18
  - **首次加载:** 说一句欢迎气泡,每个浏览器只出现一次(v2 键)。
17
- - **右键:** 弹出小菜单,含 **模型与供应商配置** 和 **OpenCode 模型监控** 两个子项,
18
- 下面两节分别说明。
19
+ - **右键:** 弹出小菜单,含 **模型与供应商配置**、**OpenCode 模型监控**(可查 **剩余用量**)
20
+ 与 **Token 用量** 三个子项,下面几节分别说明。
19
21
  - 尊重 `prefers-reduced-motion`:关闭帧动画、爱心与气泡弹跳,角色和交互仍然保留。
20
22
 
23
+ ## 双击对话(交互版)
24
+
25
+ 双击宠物会弹出一个贴着她的对话面板:她在右边放得下就弹在右侧,否则落到上方,
26
+ 都不够就移到下方,且始终夹在视口内。
27
+
28
+ | 操作 | 效果 |
29
+ |---|---|
30
+ | 双击宠物 / 聚焦后按 <kbd>Enter</kbd>、<kbd>Space</kbd> | 打开面板,她挥手并冒出一句问候 |
31
+ | <kbd>Enter</kbd> 或点 **发送** | 发送当前输入;发送中按钮禁用,不会重复提交 |
32
+ | <kbd>Shift</kbd>+<kbd>Enter</kbd> | 换行,不发送 |
33
+ | 输入法候选中的 <kbd>Enter</kbd> | 只上屏文字,不发送(检查 `isComposing`) |
34
+ | <kbd>Esc</kbd> / 点面板外 / 点宠物本人 / 点 × | 关闭面板,已聊的消息保留 |
35
+ | 标题栏 **垃圾桶按钮** | 清空对话:点一下变红进入确认,再点一次才执行;发送中禁用,失焦自动取消 |
36
+
37
+ **清空会连同模型侧上下文一起丢弃**:除了把消息重置成开场问候,还会清掉
38
+ `localStorage` 里的会话身份,下次发送会新建一个会话,所以她不会记得清空前的对话。
39
+
40
+ ### 选择模型
41
+
42
+ 对话面板标题栏、**关闭按钮左边**有一个模型按钮,显示当前模型名(过长省略)。点击它
43
+ 弹出**模型选择弹窗**:顶部是检索框,下面是两级级联。
44
+
45
+ | 位置 | 内容 |
46
+ |---|---|
47
+ | 检索框 | 输入关键词即**跨所有供应商**过滤模型;大小写不敏感,匹配模型 ID 与显示名 |
48
+ | 左列:供应商 | DeepSeek 官方账号 / 官方 API / 其余供应商,按同一规则排序;带 `›` 表示可展开 |
49
+ | 右列:模型 | 选中供应商下的全部可用模型,当前选择带 `✓` |
50
+
51
+ - 检索时左列收起,右列变成**一列匹配结果**并标出来源;清空关键词后恢复级联,
52
+ 回到上次展开的供应商。
53
+ - 检索框中 <kbd>Enter</kbd> 选中第一条匹配;<kbd>Esc</kbd> 先清空关键词,
54
+ 再按一次才关闭弹窗;无匹配时显示「没有匹配的模型」和模型总数。
55
+ - 左列切换只改右列内容,不影响已选;**点右列某个模型即选中并关闭弹窗**。
56
+ - 打开弹窗时自动聚焦检索框,并定位到当前模型所在的供应商;点弹窗外部只收起弹窗,
57
+ 对话面板保留。
58
+ - 数据来自客户端远端 `ctx.remote.session.modelCatalog()`;`routableProviders` 之外、
59
+ 暂时没有可用模型的供应商不会出现,单个供应商加载失败也不影响其他项。
60
+ - 选择记在 `localStorage`(`dsh.deepseek-pet.chat.model`,键为 `provider::model`)。
61
+ 重开面板会重新拉一次目录;记住的模型不在目录里时自动回退到第一个并写回存储。
62
+ - 目录读取中 / 失败 / 为空时按钮禁用;**没有可选模型时发送会被拦下**并给出提示,
63
+ 而不把消息挂住。
64
+
65
+ **回复已接通。** 发送走 `ctx.remote.session` 的四个方法:
66
+
67
+ 1. `create({ sessionId, agentPreset: 'pet' })`:第一次发送时建一个**专供宠物对话的会话**,
68
+ 身份是随机生成的 `pet-chat-*`,记在 `localStorage`(`dsh.deepseek-pet.chat.sessionId`)。
69
+ 同一身份下已有空白会话会被 Host 复用,所以刷新页面后还能接着聊,也不会每句话新建会话。
70
+ 人设标记记在 `dsh.deepseek-pet.chat.preset`,**人设版本变了会自动换新会话**,
71
+ 不会接着旧上下文人设聊。
72
+ 2. `selectModel({ sessionId, provider, model })`:把该会话固定到弹窗里选的模型。
73
+ 3. `state({ sessionId })`:读当前游标 `cursor`,作为本轮回复的起点。
74
+ 4. `prompt({ requestId, sessionId, mode: 'queue', content: [{ type: 'text', text }] })`
75
+ 发消息,再 `wait({ sessionId, afterSeq, maxMessages: 1, timeoutMs })` 等本轮静默,
76
+ 从返回的 `output.records` 里挑 `assistant/message` 事件,取
77
+ `event.data.message.content` 中 `type === 'text'` 的块拼出回复。
78
+
79
+ 失败时面板会显示具体原因:发送被拒、等太久(`chatErrTimeout`,默认 120 秒)、
80
+ 模型在等你在应用里确认(`chatErrPending`)、模型没给文字(`chatErrEmptyReply`)。
81
+ 失败时输入框原文会被放回,方便重试。
82
+
83
+ 消息本身不落盘,刷新页面后清空;问候语只在会话里显示,不会作为上下文发给模型。
84
+ 会话建好后第二轮起不再重复 `create`,也不再重新 `selectModel`。
85
+
86
+ **宠物会话和一个普通会话一样出现在左侧列表。** 这是刻意的:侧栏的删除类插件
87
+ (如 `dsh-session-clean`)只能操作侧栏里看得见的会话,藏起来就等于删不掉。配合要点:
88
+
89
+ - 会话 id 用 `session-<uuid>` 格式,与 Host 自己生成的一致,格式校验类插件才认;
90
+ - 每轮回复读完**之后**调一次 `session.rename({ sessionId, title })`,把标题改成
91
+ 「大肥鱼对话」,在侧栏里一眼能认出来并随时删除;
92
+ - 同一会话只改名一次,失败(返回 `ok: false` 或抛错)会清标记,下一轮重试;
93
+ - 改名失败不影响聊天本身。
94
+
95
+ **不要再给宠物会话做归档。** 实测 Host 的 `session/create` 只复用「空白」会话,
96
+ 而 `reusable` 判定还把已归档的会话排除在外;一旦归档,刷新页面后 `create` 会另起新会话,
97
+ 之前的上下文就丢了。所以现在只在**没有历史身份**时才 `create`,已有身份直接
98
+ `selectModel` + `prompt`——这两者都会由 Host 自动恢复会话并带上完整历史。
99
+
21
100
  ## 右键菜单:AI 中转站配置
22
101
 
23
102
  右键宠物(浏览器原生菜单已被屏蔽),选择 **模型与供应商配置**。这个对话框会把
@@ -60,6 +139,11 @@
60
139
  而不是直接覆盖。
61
140
  - 点击外部、按 <kbd>Escape</kbd> 或选中菜单项后菜单收起;对话框可通过遮罩、
62
141
  <kbd>Escape</kbd> 或 **取消/完成** 关闭。
142
+ - 菜单**先渲染再定位**:按点击位置落点后量真实尺寸,再收进视口(右/下溢出时反向收边),
143
+ 所以菜单变长不用改常量;高度超过窗口时菜单自身滚动(`max-height: calc(100vh - 16px)`)。
144
+ - 可展开的项(如 **OpenCode 模型监控**)用**独立二级菜单**浮在右侧,而不是把父菜单拉长:
145
+ 右侧放不下自动翻到左侧,纵向超出自动收进视口;点两个面板之外或按 <kbd>Escape</kbd>
146
+ 关闭(Escape 先收二级、再收一级)。
63
147
 
64
148
  ## 右键菜单:OpenCode Go 模型监控
65
149
 
@@ -67,6 +151,7 @@
67
151
 
68
152
  - **立即检查变动** —— 抓取一次新快照并与上一次对比。
69
153
  - **查看最近变动记录** —— 列出最近的检查记录。
154
+ - **剩余用量** —— 查询 OpenCode Go 当前还剩多少额度(见下节)。
70
155
 
71
156
  数据来源:
72
157
 
@@ -112,7 +197,7 @@
112
197
  `unlimited` 视为最高值。所选套餐与方向保存在 `localStorage`
113
198
  (`dsh.deepseek-pet.opencode.sortField`、`dsh.deepseek-pet.opencode.sortDir`)。
114
199
 
115
- 它排的是面板里展示的**月度配额上限**,不读取已消耗用量或账户余额。
200
+ 它排的是面板里展示的**月度配额上限**;账户实时用量由下面的 **剩余用量** 面板单独查询。
116
201
 
117
202
  文档里的 **Estimated requests / 5 hr** 数值作为参考一并展示。配额图会在配额数值下方
118
203
  显示一行紧凑的 `5h` 副值,跟随可见图例系列;明细页显示 Go、Go Plus、Go Teams 的精确
@@ -121,13 +206,100 @@
121
206
  图表上方的摘要标签显示模型数量、价格来自实时 API 还是文档、以及是否已配置 Key。
122
207
  没有变化的检查会显示 **没有变动**,而不是 0/0/0 的计数,同时下方仍完整展示当前状态。
123
208
 
124
- Host 半区通过 `ctx.connection.fetch.register` 注册了三个认证路由 ——
209
+ Host 半区通过 `ctx.connection.fetch.register` 注册了四个认证路由 ——
125
210
  `/api/deepseek-pet/opencode/check`(POST)、
126
211
  `/api/deepseek-pet/opencode/history`(GET)、
127
- `/api/deepseek-pet/opencode/status`(GET)。所有网络与文件操作都在这里完成,
212
+ `/api/deepseek-pet/opencode/status`(GET)、
213
+ `/api/deepseek-pet/opencode/usage`(POST)。所有网络与文件操作都在这里完成,
128
214
  浏览器端因此不需要处理 CORS。Client 半区支持热更新;Host 半区的改动要等下一次插件重载
129
215
  或 DSH 重启才生效。
130
216
 
217
+ 路由注册必须是带 `/api/` 前缀的**绝对路径**(Host 用 `fetchRoutes.get(url.pathname)` 精确
218
+ 匹配,漏了前缀就会 404)。**桌面端页面跑在 `dsh-app://` 自定义协议下**,那里发文档相对
219
+ 地址 `api/...` 会被解析成 `dsh-app://local/web/api/...`(pathname 变成 `/web/api/...`),
220
+ 与注册路径对不上而 404;因此客户端先取页面的 http(s) 同源地址拼绝对地址,只有拿不到
221
+ http(s) 来源时才退回文档相对写法。出错信息里会附上实际请求地址,便于区分是哪种情况。
222
+
223
+ ## 右键菜单:OpenCode Go 剩余用量
224
+
225
+ 右键宠物 → **OpenCode 模型监控** → **剩余用量**。面板打开时自动查一次,之后点
226
+ **刷新用量** 再查。它展示三张额度卡片:
227
+
228
+ - **5 小时** —— 滚动窗口(`rolling`);
229
+ - **本周** —— 每周窗口(`weekly`);
230
+ - **本月** —— 每月窗口(`monthly`)。
231
+
232
+ 每张卡片显示**剩余百分比**(进度条在剩余低于 30% 转橙、低于 10% 转红)、已用百分比和
233
+ 重置时间;窗口达到上限时标出 **已达上限**。这些窗口与服务端 `status: "rate-limited"`
234
+ 的语义一致。
235
+
236
+ 数据来源是 `GET https://opencode.ai/zen/go/v1/usage`(桌面版 OpenCode 用的同一个端点),
237
+ 返回的是 `{"usage":{"rolling|weekly|monthly":{"status","percent","resetsAt"}}}`。它接受
238
+ Console 的 `oc_sk_` 密钥(`Authorization: Bearer ...`),不认 `sk-cp-` 形态的 Go 连接
239
+ 密钥——用后者会得到 401,面板会直接说明这一点。Key 与模型检查共用
240
+ `OPENCODE_GO_API_KEY` 凭据,输入框留空即沿用已保存的 Key。
241
+
242
+ 每次成功查询都会把读数原子写入
243
+ `$DSH_HOME/opencode-go-models-snapshots/usage.json`(权限 0600,**只存百分比与重置时间,
244
+ 不存 Key**)。查询失败时面板展示上一次的读数并提示 **这是上次保存的读数**,所以断网也
245
+ 不会白屏。**打开 OpenCode Console** 按钮跳转到控制台查看余额与发票。
246
+
247
+ 面板上的 **来源** 按钮展开三个可选字段,保存在 `localStorage`:
248
+
249
+ | 字段 | 键 | 用途 |
250
+ | --- | --- | --- |
251
+ | usage URL | `dsh.deepseek-pet.opencode.usageUrl` | 覆盖默认用量端点,可指向能返回相同结构的代理/中转 |
252
+ | fallback URL | `dsh.deepseek-pet.opencode.usageFallbackUrl` | 首选地址失败时再试一次(例如以防某项网络策略拦住了默认域名) |
253
+ | org id | `dsh.deepseek-pet.opencode.orgId` | 团队工作区 id,作为 `x-opencode-org-id` 发送,用于取回本成员在看板上的额度 |
254
+
255
+ 地址必须是 `http(s)`;早先的 `console.opencode.ai` 域名会自动改写为
256
+ `opencode.ai/console`。
257
+
258
+ ## 右键菜单:Token 用量统计
259
+
260
+ 右键宠物 → **Token 用量**。面板打开时自动统计一次,之后点 **刷新** 可以强制重算
261
+ (跳过缓存)。面板顶部三个按钮切换统计周期:
262
+
263
+ | 周期 | 含义 |
264
+ | --- | --- |
265
+ | **本日** | 本地时区今天 00:00 到现在 |
266
+ | **本周** | 本周周一 00:00 到现在 |
267
+ | **本月** | 本月 1 号 00:00 到现在 |
268
+
269
+ 每张卡片先给出总量与调用次数,再拆出**未缓存输入**、**缓存读取**、**输出**三项;
270
+ 下面是**模型用量**列表,按该周期的总量降序,每行显示模型名、供应商标识、占比条,
271
+ 以及各自的调用次数与三项 token 明细;末尾是**与当前周期对应**的趋势柱状图。
272
+
273
+ 趋势图跟着所选周期走,不是固定的 30 天:
274
+
275
+ | 周期 | 趋势粒度 | 柱子数 |
276
+ | --- | --- | --- |
277
+ | **本日** | 按小时 | 24 根(0 点到 23 点),标题为「每小时用量」 |
278
+ | **本周** | 按天 | 本周一到今天,最多 7 根,横轴是星期几 |
279
+ | **本月** | 按天 | 本月 1 号到今天,最多 31 根,横轴是几号 |
280
+
281
+ 没有用量的时段也会留一根空柱,所以空档一眼可见。柱子下方有坐标标签,点数变多时
282
+ 自动抽稀(≤8 根全标、≤16 根隔一个标、再多就隔五个标)。
283
+
284
+ 数据来自本机 DSH 的**会话日志**(`ctx.sessionQuery`),也就是每次模型调用在
285
+ `assistant/message` 事件里留下的 `usage` 与 `source.provider` / `source.model`。
286
+ 因此:
287
+
288
+ - **缓存命中也计入总量**,所以这里的数字是「上下文体积」而不是「计费 token」;
289
+ 按全价折算会明显高于实际账单。
290
+ - **会话被删除后,这段用量就查不到了**。DSH 没有全局用量账本,删掉的会话日志连同
291
+ 投影缓存一起消失,面板只会统计还留在本机的会话。
292
+ - 统计口径是**本地时区**,时区由浏览器提供(`Intl…resolvedOptions().timeZone`)。
293
+
294
+ 两个技术细节值得一提:
295
+
296
+ - **种子会话不重复计数。** DSH 的「种子会话」会把父会话的整段历史复制进子会话,
297
+ 父子两份日志里同一批事件各出现一次。插件用日志里的 `session/end-seed` 标记找出
298
+ 继承与自有的分界,只认领自有事件;但如果父会话**已被删除**,继承来的那段就没人
299
+ 认领了,这时由子会话代为计入,避免用量凭空少一截。
300
+ - **结果有 60 秒缓存。** 会话日志重放不便宜,所以聚合结果会缓存一分钟;点 **刷新**
301
+ 可以绕过缓存强制重算。
302
+
131
303
  ## 与 Harness 界面的关系
132
304
 
133
305
  - 在横跨整帧的 `shell.overlay` 插槽注册 `deepseek-pet` 条目。该层默认点击穿透,
@@ -142,10 +314,11 @@ Host 半区通过 `ctx.connection.fetch.register` 注册了三个认证路由
142
314
  | 文件 | 用途 |
143
315
  |---|---|
144
316
  | `package.json` | bundle 清单:补丁、客户端入口、语言包与图标导出 |
145
- | `cordis.patch.yml` | 插入 `deepseek-pet` 的 Host 条目 |
146
- | `index.js` | Host 半区:OpenCode 监控认证路由与只写密钥存储 |
147
- | `monitor.js` | Host 监控核心:抓取模型、解析文档/配额、差异引擎、原子快照 |
148
- | `client.js` | 客户端模块:插槽注册、精灵动画、拖动/摸头、右键中转站配置与 OpenCode 监控界面、文案 |
317
+ | `cordis.patch.yml` | 插入 `deepseek-pet` 的 Host 条目,以及桌宠对话人设预设 `preset-pet` |
318
+ | `index.js` | Host 半区:OpenCode 监控与剩余用量认证路由、Token 用量路由、只写密钥存储 |
319
+ | `monitor.js` | Host 监控核心:抓取模型、解析文档/配额、差异引擎、原子快照、额度查询与缓存 |
320
+ | `token-usage.js` | Host 统计核心:读会话日志、按本日/本周/本月与模型维度汇总 token 用量,处理种子会话去重 |
321
+ | `client.js` | 客户端模块:插槽注册、精灵动画、拖动/摸头、双击对话面板、右键中转站配置、OpenCode 监控、剩余用量与 Token 用量界面、文案 |
149
322
  | `assets/pet-sheet.webp` | 6×3 打包精灵图集(待机 / 挥手 / 蹦跳) |
150
323
  | `locale/en.json`、`locale/zh.json` | 插件清单里显示的标题与描述(英文 / 中文) |
151
324
  | `icon.png` | 插件图标,取自同一套角色画稿 |