Dizzy-DSH 开发方案

August 24, 2026 · View on GitHub

本文档是 Dizzy-DSH 的完整开发指南:架构、平面规则、双半区开发流程、 关键技术机制、常见坑与验证手段。所有结论均来自本仓库实际踩坑验证, 而非纸面推测。


1. 架构总览

Dizzy-DSH 是一个 DSH bundle 层插件合集仓库:"克隆即装",无需 npm 发布, 重启后依然生效。每个功能 = 一个独立子包插件(与 third-party 收录的插件 同构):独立 host 插件、独立 client bundle、独立挂载/卸载,互不引用; 主包只是聚合根(依赖声明 + patch 层)。

用户机器
├── DSH 安装(dsh CLI / web 服务)
├── profile: ~/.dsh/profiles/web/
│   ├── package.json          # dependencies 含 dizzy-dsh(file: 仓库路径)
│   │                         # bundles 列表含 dizzy-dsh(自动加入)
│   └── node_modules/
│       ├── dizzy-dsh → Junction → store 快照(file: 安装时生成)
│       ├── dizzy-dsh-balance / dizzy-dsh-usage-card /
│       │   dizzy-dsh-agent-instructions / dizzy-dsh-kimi-webbridge
│       └── dsh-better-sidebar / dsh-subscription-auth / dsh-gui-customization / @anionex/...
└── 仓库(本目录)
    ├── package.json          # 聚合根:main + dsh.bundle + file: 依赖(plugins/* + 第三方)
    ├── cordis.patch.yml      # bundle 插件层(insert 条目,全部插件在此挂载)
    ├── index.js              # 聚合根空插件(无功能)
    ├── plugins/              # 自有插件合集
    │   ├── balance/          #   dizzy-dsh-balance:package.json + index.js(Host) + client.js(UI)
    │   ├── usage-card/       #   dizzy-dsh-usage-card:同上
    │   ├── agent-instructions/ # dizzy-dsh-agent-instructions:package.json + index.js + prompts/
    │   └── kimi-webbridge/   #   dizzy-dsh-kimi-webbridge:Host 工具集
    └── third-party/          # 收录的第三方插件(git subtree 跟随上游)
        ├── dsh-subscription-auth/ # OAuth 订阅登录(上游快照 + 本地补丁)
        ├── dsh-gui-customization/ # 界面设定/时装工坊(上游插件包子目录,sparse 覆盖)
        └── …                  # genui / notification / vision-toolkit / anchored-standard(subtree)

安装与生命周期

# 一条命令安装全部插件(自有子包 + 收录的第三方插件,见 §7.5)
dsh plugin --profile web add file:<仓库绝对路>
  1. dsh plugin 转发给 pnpm 在 profile 目录执行安装。必须用 file: 而非 link::link: 协议下 pnpm 不解析目标包的 dependencies(目标目录 自管依赖,已实测:link 安装的包其依赖一律不进 lockfile/node_modules), 而 file:递归安装完整依赖树(registry 依赖、file: 依赖全部解析, 经 profile 的 nodeLinker: hoisted 提升到顶层 node_modules)
  2. 主插件 package.jsondependencies 声明自有子包 (file:./plugins/*)与收录的第三方插件(dsh-better-sidebar@0.15.0 走 registry、其余含 dsh-subscription-auth 走仓库快照)—— file: 安装时自动全部带上
  3. 安装成功后 reconcile(plugin-9h8shc4d.jsreconcilePlugins): 遍历 profile 顶层 dependencies,凡是解析到的包声明了 dsh.bundle.patch 就自动加入 dsh.profile.bundles 层列表(顶层只会有 主插件一个,子包与第三方由主插件 patch 挂载,见第 4 步; 子包不带 dsh.bundle.patch,add 时会提示 "plain dependency",属预期)
  4. 下次启动 dsh web 时,loader 读取主插件 bundle 的 cordis.patch.yml, 其中的 - insert: 条目列出全部插件行(自有子包 + 收录的第三方), entry 的 name 是包名,从 profile 顶层 node_modules 按包加载:
    • Host 半区:import('<包名>') → 包 main(index.js)→ 挂载插件
    • Client 半区:client-modules 按 entry 的包名扫描 dsh.client 声明 + exports["./client"] → 把该包的 client bundle 注入浏览器
  5. 首次安装若遇 ERR_PNPM_IGNORED_BUILDS(node-pty/protobufjs 构建脚本): pnpm 默认禁止并以非零码退出导致 reconcile 不执行。在 profile 的 pnpm-workspace.yamlallowBuilds 里将两者设为 true 后重跑 (pnpm v11 会自动生成占位 node-pty: set this to true or false)

卸载:dsh plugin --profile web remove dizzy-dsh(子包与第三方随依赖一起移除) 更新:cd <仓库> && git pull强制同步 file: 快照 —— pnpm 对 file: 依赖只检测 package.json 是否变化,仓库内其他文件(如 plugins/agent-instructions/prompts/agent-instructions.md)改了不会同步到 node_modules 的安装副本(已实测复现:改 prompt 文件后 dsh plugin add 报 "Already up to date",system 里仍是旧内容)。强制同步(主包与每个子包 都要删):

Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh-balance -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh-usage-card -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh-agent-instructions -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh-kimi-webbridge -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dsh-subscription-auth -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dsh-gui-customization -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/dsh-plugin-memory-tencentdb -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/@tencentdb-agent-memory -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/@anionex -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/@dsh-external -Recurse -Force
Remove-Item ~/.dsh/profiles/web/node_modules/@omdsh-dev -Recurse -Force
dsh plugin --profile web add file:<仓库绝对路径>

注入内容动态读取,同步后下一轮对话即生效;改了插件代码才需重启


2. 平面规则(决定能力放哪)

能力位置说明
模型工具、定时任务、数据服务、HTTP 路由对应子包 Host 半区(bundle 层)进程级,全会话共享
浏览器 UI(徽章、用量视图)对应子包 Client 半区(dsh.client)随 bundle 持久加载
每会话独立的配置(prompt、persona、技能集)agent preset(~/.dsh/.agent-presets/)每会话独立挂载/卸载
临时扩展、原型验证动态插件(cordis_define)进程级,重启即失

判断准则

  • 能力需要跨会话共享被浏览器访问 → 子包插件(bundle 层)
  • 能力只属于"某个会话的 agent 行为" → agent preset
  • 能力只是临时调试/验证 → 动态插件(用完即弃)

⚠️ 动态插件的最大局限:进程重启后全部丢失(这正是早期余额徽章 重启消失的原因)。任何"希望长期存在"的插件都必须固化为子包。


3. 双半区开发模型

每个子包插件的形态是"一个包,双半区"(以 usage-card 为例):

浏览器 (client.js)                     Host (index.js)
─────────────────                      ─────────────────
用量视图(「用量」Tab)               会话日志聚合(每日 token)
    │  fetch GET /dizzy/usage           │
    │◀───────────────────────────────   ├─ 扫描 ~/.dsh/sessions(增量)
    │                                    └─ webServer 路由 /dizzy/usage

核心原则

  1. 密钥只活在 Host:浏览器拿不到 API key / 凭据,所有敏感操作留在 Host 半区,Client 通过同源 HTTP 路由中转。
  2. Client 免构建:client.jswindow.__ModuleLoader__.load({ id, factory }) 工厂格式,factory 内的 require("react") 由平台 seed 提供, 不需要 TypeScript 编译或 bundler 打包,改完即生效。
  3. 一功能一子包:每个功能 = plugins/<name>/ 独立包(host + client + 自己的 package.json),主包只聚合;不把新功能塞进既有子包。
  4. 改动即提交:file: 是安装时快照语义,仓库即线上代码;git pull 后 删除 profile 里 node_modules/dizzy-dsh* 副本并重新 dsh plugin add file:<仓库> 同步,再重启。

4. Host 半区开发(plugins//index.js)

插件骨架

export default {
  name: 'dizzy-dsh',
  inject: ['credentials', 'timer', 'tools', 'webServer'], // 按需声明
  apply(ctx) {
    // ── 初始化 / 定时任务 ─────────────────────────────
    const refresh = async () => { /* ... */ }
    refresh()
    const stopTimer = ctx.interval(refresh, 60000)   // 60s 一次

    // ── HTTP 路由(供 Client 半区取数)─────────────────
    const stopRoute = ctx.webServer.register({
      kind: 'exact',                                   // exact | prefixes
      path: '/dizzy/balance',
      handler: async (req, res) => {
        res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })
        res.end(JSON.stringify(cache))
      },
    })

    // ── 模型可见工具 ─────────────────────────────────
    const disposeTool = ctx.tools.register({
      name: 'balance_check',
      description: '...',
      parameters: { type: 'object', properties: {}, additionalProperties: false },
      output: {
        schema: { type: 'string' },
        render(_args, value) { return [{ type: 'text', text: String(value) }] },
      },
      async execute() { return '结果' },
    })

    // ── 系统提示词注入(可选)────────────────────────────
    const disposePrompt = ctx.systemPrompt.section({
      name: 'dizzy-dsh:agent-instructions',   // 唯一名,重复注册抛错
      order: -50,                              // 负数 → 在 persona(0)之前渲染
      text: () => readFileSync(PROMPT_FILE, 'utf8'),  // 每次组装动态读取
    })

    // ── 清理:停止/卸载时执行 ─────────────────────────
    return () => { disposePrompt(); stopTimer(); stopRoute(); disposeTool() }
  },
}

可注入服务(先查询再使用)

写代码前用 cordis_inspect_query(Host Service provider)确认签名 (这些 inspect 工具只在官方「创造模式」会话里;DIY 模式不挂 tool-cordis):

Service.listService { }               → 服务目录
Service.listService { service: "x" }  → 精确契约

常用服务:credentials / timer / tools / webServer / fs / shell / subprocess / settings / agentDefaultModel / llm。

静态插件 vs 动态插件的环境差异

能力静态插件(bundle)动态插件(沙箱)
全局 fetch✅ 直接可用❌ 被禁用(提示走 ctx.web)
import 其他包✅ Node ESM 可用❌ 沙箱禁用
生命周期随 profile 持久进程重启即失
网络/进程直接用必须经服务中转

早期余额插件在动态沙箱里被迫用 subprocess + curl.exe 绕行, 且环境变量名撞上 SENSITIVE_ENV_PATTERN(/KEY|PASSWORD|SECRET|TOKEN/i) 被 scrub 导致 401。固化到 bundle 后一行 fetch 解决 —— 新功能优先写成 bundle 静态插件,不要从动态插件起步。


4.5 系统提示词注入(systemPrompt)

对 DSH 系统提示词动手脚的正规入口是 ctx.systemPrompt 服务(bundle 层注册 = 全局生效,所有会话、所有工作区都包含):

方法作用本仓库用法
section({ name, order, text })注册系统提示词段落,按 order 升序拼接dizzy-dsh:agent-instructions,order -50
context({ name, order, text })注册动态上下文(user-role 快照)暂未用
variable(name, provider)注册 {{variable}} 插值变量暂未用
suppressRuntimeContext()抑制动态运行时上下文慎用,勿全局抑制
tools(provider)注册工具 schema 提供者见 tools.register

order 约定(查证 dsh-system-prompt 源码)

-100   harness 身份(最先)
-50    ← 本仓库注入的 Agent 规则
 0     persona(部署人格 / agent preset 覆盖)
100+   工具指南

负数段在 persona 之前渲染,适合"规则/约束"类内容;complete: true 会独占整个系统提示词,绝不用于注入(会覆盖 harness 身份)。

内容动态读取

text 支持函数,每次模型步骤组装时求值 → 编辑 prompts/agent-instructions.md 无需重启,下一个模型步骤即生效:

const disposePrompt = ctx.systemPrompt.section({
  name: 'dizzy-dsh:agent-instructions',
  order: -50,
  text: () => readFileSync(PROMPT_FILE, 'utf8'),
})

与 DSH 内置 agent-instructions 的分工

DSH 内置 agent-instructions本仓库注入
注入路径user-message(会话历史)system-prompt section
内容来源工作区的 AGENTS.md/CLAUDE.md仓库 prompts/agent-instructions.md
依赖工作区存在该文件全局,不依赖工作区
优先级作为用户消息系统提示词,模型最先读取

两者名字不同、注入路径不同,同时存在不冲突,互补使用。

5. Client 半区开发(plugins//client.js)

免构建 ModuleLoader bundle 骨架

window.__ModuleLoader__.load({
  id: 'dizzy-dsh',              // 必须等于 entry 的包名
  factory: (require) => {
    var module = { exports: {} }
    var exports = module.exports
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })

    const React = require('react')   // 平台 seed,免 import

    const apply = (ctx) => {
      const slots = ctx.get('slots')
      if (slots === undefined) return
      // 注册 Slot UI(创造模式里先 cordis_inspect_query 查 Slots.listSubTree)
      slots.inject('conversation.input.right', () => slots.register(
        { name: 'conversation.input.right', id: 'deepseek-balance', label: 'DeepSeek 余额' },
        (props) => React.createElement(Badge, props)
      ))
      return () => { /* 清理 */ }
    }

    exports.apply = apply
    return module.exports
  },
})

组件内取数(经 Host 路由)

// 浏览器真实环境:fetch / setInterval 全局可用(非动态守卫)
const response = await fetch('/dizzy/balance', { credentials: 'same-origin' })
const r = await response.json()
const timer = setInterval(load, 60000)
// 清理:return () => { clearInterval(timer) }

Slot 选择

cordis_inspect_query(Client Slots provider)listSubTree 查实时插槽树 (同样只在创造模式;DIY 对照本仓库已有 client 插件即可), 再对精确 root 查完整契约(ownerProps / registration / occupants)。常用:

Slot用途
conversation.input.right输入栏模型选择器左侧(本仓库徽章位置)
conversation.input.left输入栏工具行左侧
conversation.composer.dock输入栏下方状态行
conversation.view会话视图环(本仓库「用量」Tab 挂载点:一 entry 一 Tab,宿主只渲染激活视图;chat=0/trajectory=10,新视图取 order 20)
conversation.session.header.utilities会话页头右侧列表(加法,不替换页头)
conversation.session.headersingle 槽,宿主已占 priority 0;占用即替换整条页头,本仓库不用
settings.section设置页(完整页面)
tool.view.cordis动态插件 Run 卡片内交互区

Client 端依赖声明

package.jsondsh.client.inject 控制 client bundle 的加载顺序依赖 (不是服务注入):

"dsh": {
  "client": {
    "platform": "web",
    "inject": ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-ui-conversation"]
  }
}

如果新 UI 依赖其他 client 包的 Slot/服务,按需追加(创造模式里先查 cordis_inspect_list 确认 client 服务是否存在;DIY 对照已有 client 插件)。


6. 关键技术机制(踩坑总结)

6.1 loader 相对/包名解析

  • entry name. 开头 → 相对组合文件目录解析(new URL(name, baseUrl))
  • entry name 是包名 → 从 profile 的 node_modules 解析
  • bundle 层 entry 必须用包名(dizzy-dsh),不能是 dizzy-dsh/plugins/x.js 子路径 —— client-modules 按 entry 名解析 require.resolve(<entryName>/package.json) 找包声明, 子路径会导致解析失败、client 半区不加载。

6.2 ESM 声明

package.json 必须有 "type": "module"(或插件文件用 .mjs), 否则 Node 把 .js 当 CJS,export default 直接语法错误。

6.3 环境变量 scrub

subprocess 会剔除环境中的 KEY|PASSWORD|SECRET|TOKEN 命名的变量和全部 DSH_* 前缀变量。不要把敏感值放环境变量传给子进程;直接用 credentials 服务 + 全局 fetch,或显式 argv 传参。

6.4 webServer 路由契约

ctx.webServer.register({
  kind: 'exact',        // exact(精确路径)或 prefixes(前缀,最长匹配)
  path: '/dizzy/balance',
  handler: async (req, res) => { ... },  // node:http 风格
})
// 返回 disposer;重复 (kind, path) 抛错

6.5 client-modules 扫描链

loader entry(name=包名) → require.resolve(包名/package.json)
  → dsh.client.platform === 'web'
  → exports["./client"] → 读 bundle 文件(缺失抛 MissingClientBundleError)
  → 注入浏览器 window.__DSH_BOOT__ → ModuleLoader 加载

7. 添加新功能(新子包)的完整流程

# 0. 建子包目录 plugins/<name>/
#    package.json:name = 包名,声明 main + exports["./client"] + dsh.client
# 1. Host 逻辑 → plugins/<name>/index.js
#    - 数据/工具/路由加入 apply,返回 disposer;name 用子包名

# 2. Client UI(如果需要)→ plugins/<name>/client.js
#    - window.__ModuleLoader__.load({ id: <包名>, ... })
#    - slots.inject 注册目标 Slot
#    - 数据经 ctx.webServer.register 的新路由获取

# 3. 主包 package.json 的 dependencies 加 "包名": "file:./plugins/<name>"
#    cordis.patch.yml 加一行 entry(name = 包名)

# 4. 验证
Remove-Item ~/.dsh/profiles/web/node_modules/dizzy-dsh* -Recurse -Force
dsh plugin --profile web add file:<仓库绝对路>
dsh --profile web --dump-config          # 新 entry 是否挂载
#    浏览器刷新,检查 UI 是否出现

# 5. 提交
git add -A && git commit -m "feat: ..." && git push
#    用户侧:git pull + 删副本重装 + 重启 dsh web

验证清单

  • dsh --dump-config 输出包含 # == dizzy-dsh 段,且该段出现全部 entry(balance / usage-card / dizzy-agent-instructions / kimi-webbridge / dsh-subscription-auth / better-sidebar / dsh-vision-toolkit / genui / dsh-notification / ui-gui-customization)
  • Host:每个子包可加载且 name/inject 正确: node --input-type=module -e "import('dizzy-dsh-balance')", import('dizzy-dsh-usage-card')import('dizzy-dsh-agent-instructions')import('dizzy-dsh-kimi-webbridge')import('dsh-subscription-auth')
  • node --test plugins/balance/grok-parse.test.js 全绿
  • 收录的第三方可加载(依赖齐全): import('dsh-better-sidebar')import('@anionex/dsh-vision-toolkit')import('dsh-gui-customization') 均不报 Cannot find package ...(link: 安装的典型症状); dsh-gui-customization 是空 Host(typeof apply === 'function'),无 name/inject
  • 每个有 UI 的子包 Client:exports["./client"] 指向的文件存在,含 window.__ModuleLoader__.load 且 id 等于包名
  • 浏览器:目标 Slot 出现 UI,数据路由返回正确 JSON
  • 重启 dsh web 后功能仍在(验证持久化)
  • 提示词注入:新会话中系统提示词包含注入规则(可在对话中询问 agent 是否遵循 First-Principles 等规则验证)

7.5 收录第三方插件(third-party)

仓库收录他人已做好的 DSH 插件快照,供"克隆即装",同时明确保留上游 地址与来源标注。规范:

  • 目录:third-party/<包名>/,包名与插件 package.jsonname 一致 (如 DSH-better-sidebar 对应 dsh-better-sidebar)
  • 每个目录必须有 UPSTREAM.md:上游仓库收录版本上游 commitLicense收录方式更新方式本地安装 命令
  • 快照不改:收录内容与上游一致(允许整文件格式层同步),功能修改 一律提交到上游;本地有未提交补丁时在 UPSTREAM.md 中注明
  • 排除 .gitnode_modules__pycache__*.tgz(.gitignore 已兜底)
  • README「收录的第三方插件」表格同步登记:插件名 / 上游链接 / 版本 / 收录位置 / 说明

安装收录的插件不需要单独 add:主插件 package.jsondependencies 声明了它们(dsh-better-sidebar@0.15.0 registry + @anionex/dsh-vision-toolkit file:./third-party/... 快照),一条 dsh plugin add file:<仓库> 全部安装 并随主插件 patch 一起挂载:

dsh plugin --profile web add file:<>
dsh --profile web --dump-config   # # == dizzy-dsh 段出现全部 entry(含 ui-gui-customization)

已验证(tmp-file* 临时 profile 完整实测):file: 安装主插件时 pnpm 递归解析其 dependencies —— better-sidebar 及其全部运行时依赖 (ws/codemirror/xterm/node-pty 等)从 registry 安装,vision-toolkit 快照 及其依赖 saxes 一并装入,hoisted 提升到顶层 node_modules,自有与收录 entry 全部 import 成功、dump-config 全部出现。link: 方案则不行(不装依赖, better-sidebar 缺 ws 直接 import 失败)。首次安装记得配置 pnpm-workspace.yamlallowBuilds(node-pty/protobufjs → true), 否则 pnpm 以 ERR_PNPM_IGNORED_BUILDS 非零退出、reconcile 不执行。

更新上游快照:重新获取发布包/同步 checkout(见各 UPSTREAM.md),更新版本 与 commit 记录后提交。


7.6 本月用量视图(usage view)

会话页头的「用量」Tab(conversation.view 视图环,列在「对话」「轨迹」 右侧),整页仪表面:统计卡 + 月度热力图与近 7 天曲线 + 今日明细(分模型)+ 北京时间峰谷状态。 主数字是本月合计,热力图/明细/时钟都是次级信息。

实现
数据Host 扫描 ~/.dsh/sessions/**/session.jsonl.zstd。DSH 0.1.1-rc.2 起按 token-meter:每步 assistant/chunk { type: 'usage' } 先入账,同 turn/step 的 assistant/message.usage 覆盖(失败请求只留 chunk 也计入);旧日志只有 message.usage 时按条累计。模型归属取 message 的 data.message.source(provider/model,缺省 unknown)。金额按本地价 > DeepSeek 官网峰谷价 > OpenRouter 聚合价。增量刷新(文件 mtime+size 变化才重读,30s TTL)
路由GET /dizzy/usage?month=YYYY-MM{ month, days, total, detail, scannedAt, errors }:days 保持「日期 → 总 tokens」(后向兼容),detail = { days: 逐日 input/output/cacheRead 分项, recent7: 近 7 天(与查看月无关,含零用量天), today: 今日分模型 };errors > 0 时副标题提示「N 个日志文件解析失败,用量可能被低估」。旧 Host(无 detail)下 client 自动退化:弹窗只显总量、今日明细显重启提示
挂载Client 注册 conversation.view list 插槽(id: 'usage'order: 20label: '用量';chat=0、trajectory=10)。宿主把每个 entry 投影为页头 Tab,renderSlot(..., { only: activeId }) 一次只渲染激活视图;选中状态存于宿主每会话 store(persist: dsh.conversation.chat),刷新页面保持;插件卸载后宿主 resolveActiveView 自动回退 chat。视图本身是普通整页流,不需要 portal;悬浮读数是视图内的 position:fixed 弹窗,跟鼠标并避让视口边缘
热力图周一起始 7 × 周数 网格(34px 格,行=周一~周日、列=周),格内日期数字;DeepSeek 蓝阶四档(lv1–lv4,按当月峰值比例分档),月外 visibility:hidden;今日描边+脉冲;hover/focus 浮层显示日期 + 总量 + 输入(未命中)/输入(命中缓存)/输出
曲线热力图右侧「近 7 天」用量曲线(detail.recent7):Catmull-Rom 转三次贝塞尔平滑过点 + 面积淡填;鼠标按横坐标吸附最近一天,竖线跟随鼠标 X,弹窗跟鼠标并避让视口边缘;旧 Host 退化为查看月内 7 天
今日明细按模型分行的统一蓝底堆叠条:输入未命中(蓝) / 输入命中缓存(青绿) / 输出(琥珀);条长相对当日峰值,段宽相对该模型三项之和;缺分项时整条纯蓝;旧 Host 无 detail 时显示重启提示
月份切换页头 ‹ › ±1 月;点月份打开年/12 月格 + 「回到本月」;Esc / 点外侧关闭。60s 自动重取 + 手动刷新按钮;数据按 data.month === 查看月 判定可见(切月即骨架,旧月数据不串月),同月刷新静默
时钟独立 PeakClock(不拖整页重绘);底栏小圆点 + HH:MM + 峰谷标签;色从 --dsw-static-green-500 / --dsw-static-red-500 读 rgb 再渐变
外观居中栏(max-width 860px),全部吃宿主 --dsw-* token(明暗主题跟随);纵向滚动由宿主 scrollBody 提供,视图只管内容流;scrollBody 与对话共用,激活时主动 scrollTop = 0 回顶(对话自身有每会话滚动位置存档,不受影响)

注意:DeepSeek 官方 API 无按天用量接口。DSH 0.1.1-rc.2 token-meter 以 每步 assistant/chunk { type: 'usage' } 为样本,同 turn/step 的 assistant/message.usage 覆盖该步;本视图按同一规则聚合本地会话日志, 与官方控制台「用量」页口径可能不同。 zstd 多帧解压逻辑复刻自 @deepseek-ai/dsh-session-persistence-jsonlscanZstdFrames(按 block 遍历,不依赖 FCS),插件不能 import 该包(profile 的 node_modules 里没有 @deepseek-ai/*)。


7.7 余额徽章的 Grok 模式(dizzy-dsh-balance)

输入栏右侧同一徽章(conversation.input.right / id: deepseek-balance): deepseek-official 显示 ¥,grok 显示 SuperGrok 周额度剩余百分比。 不是 grok.com 网页的 2h 查询桶。

实现
凭证读订阅插件写入的 GROK_SUBSCRIPTION_TOKEN(JSON:refresh/access/expires);过期或 401 时 POST https://auth.x.ai/oauth2/token 续期,写回前再读盘合并,热登录换了 refresh 不覆盖
上游GET https://cli-chat-proxy.grok.com/v1/billing?format=credits,头 Authorization: Bearer + X-XAI-Token-Auth: xai-grok-cli + x-grok-client-mode: interactive。可选再打 /v1/settingssubscription_tier_display
账本只用 config.creditUsagePercent + config.currentPeriod;省略 percent 且有 period = 真实 0%;禁止monthlyLimit/used 反推。展示已用 floor,剩余 100 - floor(已用)
路由GET /dizzy/grok-quota{ status, remainingPercent, creditUsagePercent, periodEnd, subscriptionTier, error, at }。同源校验;响应不含 token / email / accountId
工具grok_quota_check(无参);未登录提示去设置 → 订阅服务
挂载仍是 dizzy-dsh-balance 的同一 slot,不另开插件

企业代理把 dizzy-balance.grokBillingBaseURL 指到自建 cli-chat-proxy(/v1 结尾)。不要读 ~/.grok/auth.json,也不要打 grok.com gRPC-web。

8. 常见问题排查

症状原因处理
重启后功能消失动态插件(进程级)固化为 bundle 层
客户端 UI 不加载entry name 不是包名 / exports["./client"] 缺失 / client.js 文件缺失对照 §6.5 扫描链逐环检查
export default 语法错误"type": "module"package.json 补声明
余额显示 不更新Host 路由未注册 / 缓存未刷新检查 webServer 路由与 interval
Grok 徽章不出现 / 一直「未登录」当前模型不是 provider === grok,或尚未走订阅登录,或 file: 快照未同步切到 Grok 模型;设置 → 订阅服务登录;删 node_modules/dizzy-dsh-balance 后重装合集
401 授权失败敏感 env 被 scrub / key 未配置用 credentials + fetch,检查 DEEPSEEK_API_KEY
patch 不生效改完没重启 / link 指向旧路径重启 dsh web;确认 bundles 列表
重复路由报错(kind, path) 重复注册换路径或复用已有注册
duplicate loader entry id: agent-instructionspatch 里用了官方已占用的 entry id改成 dizzy-agent-instructions;官方 id 即使用 disabled: true 也仍占着
single slot "conversation.session.header" already has a registration at priority 0误占宿主 single 槽改挂 conversation.session.header.utilities(list);不要用换 priority 去 shadow 整条页头
corrupt Zstandard session log: first frame is not exactly one header line某份 session.jsonl.zstd 的第一帧明文不是单独一行 header(常见于把多帧日志解压后再一次性压回);官方 workspace list() 对这类文件零容忍,整棵插件树起不来。不是 Dizzy-DSH 插件运行时逻辑错误从仓库根跑 node scripts/repair-zstd-header-frame.mjs 先 dry-run(只报 session.jsonl.zstd,不改盘);确认 actionable 后加 --apply。解压明文与 .bak 完全一致才还原多帧备份,否则拆成「header 一帧 + 其余一帧」(能过 list,不等于恢复官方逐批追加的帧布局)。空文件/撕坏首帧官方会跳过,脚本也 leave,除非旁边有可用 .bak。修完仍可能因同目录残留 session.jsonl、重复 session id、header 路径对不上而起不来
history unavailable + uncachedInputTokens / Too small: expected number to be >=0订阅插件(Kimi Anthropic)曾把净增量 input_tokens 再减 cache,写出负 usage;DSH tokenUsage schema 非负校验会让 session.history 整页失败已在 third-party/dsh-subscription-auth 写入侧 mapUsage 钳零,读路径 projection-guard 按 unit 钳非负(本地补丁,见 THIRD-PARTY-PATCHES.md)。不要改 DSH 内核。旧日志仍炸则换新会话;改完重启 dsh --profile web
duplicate loader entry id: dsh-subscription-authprofile 的 cordis.patch.yml 仍单独 insert 了试装行,与合集 bundle patch 撞 id删掉 ~/.dsh/profiles/web/cordis.patch.yml 里那条 insert,并去掉指向仓库外的 junction
duplicate loader entry id: ui-gui-customization本机曾单独 dsh plugin add dsh-gui-customization,与合集 bundle patch 撞同一 iddsh plugin --profile web remove dsh-gui-customization,再重装合集

9. 与动态插件 / agent preset 的协作

  • 动态插件仍可用于:临时调试、原型验证、需要审批流程的交互式工具 (tool.view.cordis 面板)。成熟后固化进本仓库。工具集只挂在官方 「创造模式」(cordis preset 的 tool-cordis);cordisInspect 是进程 单例,第二个含该行的 preset standing mount 会抛 already registered
  • DIY 模式(presets/diy/~/.dsh/.agent-presets/diy):持久造物 preset,不挂 tool-cordis,与创造模式同进程共存。动态探测请另开创造模式。
  • agent preset(~/.dsh/.agent-presets/):管理 persona、每会话工具集、 技能目录。本仓库是 Host 平面;两者互补,不冲突。
  • 本仓库插件发布服务时,服务名全局唯一(进程级注册表),避免与 profile 其他 bundle 撞名。

文档版本:1.1(2026-08-22,对齐 DSH 0.1.1-rc.2)。所有机制均经实际验证;修改架构前请先在 创造模式用 cordis_inspect_query 确认当前运行时契约,再更新本文档。