DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

chiqianyushao /

chiqianyushao/dsh-service-status

Verified

DeepSeek service status as dots beside the composer and in the sidebar, driven by configurable status.deepseek.com Atom/RSS feeds, plus a model_status tool. Zero runtime dependencies.

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@3705ffd4

dsh-service-status

ci license

把 DeepSeek 的服务/模型状态搬进 DSH:输入框工具栏左侧和侧边栏底部各一枚状态圆点,点开是 status.deepseek.com 的现场情况(整体状态、被点名的服务与模型 API、最近事件原文链接); 两枚圆点各自可以关掉,订阅地址可以在设置页里改;外带给模型一个 model_status 工具, 让它在判定「这次调用失败到底怪谁」之前能先问一句。

围绕 harness 0.2.0-rc.2 / DSH Desktop 0.12.0(2026-10 那一版)写成。插件版本 0.3.0。 零运行时依赖:host 半只有 ESM,浏览器半只 require('react') —— 弹层既不用 react-dom 的 portal (它是一进 top layer 就会和锚点脱钩的那类定位),也不依赖任何图标包。

English — DeepSeek service status (status.deepseek.com) inside the harness UI: a status dot beside the composer and at the sidebar foot (each switchable), a detail popover over configurable Atom/RSS feeds, a settings page, and a model_status tool so the model can ask "is this failure ours or theirs?" before blaming a tool call.

安装

dsh plugin add chiqianyushao/dsh-service-status

或者用 GitHub Release 上预构建的 tarball(同样不需要构建,适合不想走 git 依赖的环境):

dsh plugin add https://github.com/chiqianyushao/dsh-service-status/releases/latest/download/dsh-service-status.tgz

DSH Desktop:侧栏 插件 → 添加插件 → 粘贴 https://github.com/chiqianyushao/dsh-service-status → 安装 → 重启 DeepSeek Harness。桌面端的 profile 由 Electron 独占,所以不要用 dsh plugin --profile desktop … 装。

仓库里已提交 lib/,因此从 GitHub 安装时不需要构建、也不需要授权构建脚本。

改名说明:本插件原名 dsh-model-status,与 npm 上已有的同名包冲突,公开发布时改为 dsh-service-status。配置文件位置随之变为 <harness home>/service-status/config.json;旧的 <harness home>/model-status/ 目录可以删掉 —— 设置会回到默认值,重新保存一次即可。


一、用起来是什么样

你在哪 看到什么 点一下
输入框工具栏左侧(模型选择器同一行) 圆点 + 服务正常 / 服务降级 / 服务异常 / 状态未知 展开详情面板
侧边栏底部(设置按钮旁) 同上;侧栏收起成 56px 轨道时只留圆点 同上
设置 → 服务状态 两个「显示位置」开关 + 订阅地址列表(可增删、可测试) ——
模型上下文里 model_status 工具:状态、未解决事件、相关分量、事件链接 ——

面板内容:标题旁的状态胶囊、更新于 N 分钟前、读取失败时的黄色提示(仍显示上一次结果)、 被最近事件点名过的服务/模型(最严重的在前)、最近 5 条事件(状态词 + 相对时间 + 原文链接), 以及通往状态页的入口。

状态只有四个:

圆点 词 什么时候
绿 服务正常 最近窗口里没有未解决事件
黄 服务降级 最新未解决事件处于 monitoring / 计划维护
红 服务异常 最新未解决事件处于 investigating / identified
灰 状态未知 一次都没读到(不是「挂了」)

「降级」「异常」的圆点会带一圈同色光晕,所以扫一眼侧边栏就知道现在不是绿的,而不用读字。


二、设置:显示在哪、读哪些地址

设置页在 设置 → 服务状态(settings.section 席位),做三件事:

  1. 显示位置:两个开关,分别控制输入框工具栏与侧边栏底部。关掉的那一处完全不占布局 —— 列表槽位会把组件返回的 null 直接丢掉,所以不会留下一个空白的间距。
  2. 订阅地址:一行一个 URL,可以加、删、改;每一行带「测试」按钮,点它走的是生产同一份读取代码 (同样的超时、同样的解析),能当场告诉你「连接正常 · N 条事件」还是失败原因。
  3. 保存 / 恢复默认:保存后立即生效,两枚圆点当下就会出现或消失;面板也会立刻用新地址重读一次。

配置文件位置:<harness home>/service-status/config.json($DSH_HOME 为 Desktop 的 harness 目录), 内容是 { version, seats: { composer, footer }, feeds: [...] }。写入是原子的(临时文件 + rename), 所以断电或崩溃不会留下半个配置把插件弄哑。文件坏了或读不出来时回到默认值并记一条日志 —— 插件继续工作, 只是忘掉你的自定义。

地址的规则:只接受 http/https;最多 8 个;重复地址会被去掉;全部无效时回退到默认的两个地址 (一个什么都不读的插件只会一直说「未知」,和坏掉没有区别)。


三、两个订阅地址为什么只能由 host 去读

https://status.deepseek.com/feed.atom 与 feed.rss 是同一批事件的两种格式,都不带 CORS 头 (实测响应里没有 access-control-allow-origin)。DSH 的页面跑在 http://127.0.0.1:<port>, 直接 fetch 会被浏览器拦下,所以读 feed 这件事只能放在 host 半,浏览器半读 host 归一化后的 一份快照:

Atom  ─┐
       ├─ host: 解析 → 归一 → 60s 缓存 ─→ GET /dsh-service-status/status ─→ 两枚圆点 + model_status 工具
RSS   ─┘        (按 config.feeds 顺序读,第一个成功的就用)

设置页 ─→ GET  /dsh-service-status/config   (读配置 + 默认值 + 展示用 home)
       ─→ POST /dsh-service-status/config   (写配置,校验失败返回 400)
       ─→ POST /dsh-service-status/check    (用生产读取器试一个地址)

为什么这些路由不在 /api 下。 /api 这个前缀由 @deepseek-ai/dsh-client-connection 拥有: 它是 gateway 的载体、自带鉴权、并且会回答它不认识的那些请求。把精确路由塞进别人的前缀里虽然 按匹配顺序能生效(精确表先于前缀表),但那是在别人的命名空间里放第二个所有者。 /dsh-service-status/* 是没人认领的路径,只由本插件回答;同时仍然加了 loopback 栅栏 (socket 地址 + Host 头 + 拒绝带转发头的请求)—— 状态路由只是读公开数据,但配置路由会写文件, 栅栏在那里是承重的。

为什么一次失败不等于「挂了」。 读失败时保留上一份快照并标 stale,面板写清原因; 只有一次都没成功过才显示 状态未知。「我连不上状态页」和「DeepSeek 挂了」是两件事实, 用同一个红色说出去就是在编数据。


四、聚合规则

feed 里每条事件带一个 Status: 词和一组 Affected components:。整体状态取自 最新一条未解决事件,而不是最新一条事件——状态页顶上挂着的已恢复事件不代表现在是降级的:

feed 里的状态词 聚合
resolved / completed / closed 服务正常
monitoring / scheduled / maintenance 服务降级
investigating / identified / incident / major_outage / partial_outage / degraded_performance 服务异常
认不出来的词 状态未知(不猜)

分量表按事件从新到旧折叠,每个分量保留最新一次点名它的那条事件;同一分量被多条事件点名时 先到的赢(scripts/verify.mjs 有一条断言专门盯这个:改回去让旧的覆盖新的,套件会红)。 只列当前事件窗口里被点过名的分量——一年没人提过的分量不叫「正常」,叫「没数据」,把它标绿同样 是在编数据。


五、视觉:借壳的度量,但底是不透明的

弹层的几何、投影和配色直接取 harness 自己的值,所以它在浅色/深色下都像原生卡片:

用到的 token 值 来自
--dsw-radius-sm / md / lg 8 / 12 / 16px 全局控件圆角
--dsw-alias-bg-layer-2 主题的抬升层(不透明) 弹层底色
--dsw-alias-border-l2 #0000001a / #ffffff1f 弹层描边
--dsw-elevation-prominent stroke + 两层阴影 卡片投影
--dsw-alias-interactive-bg-hover/active #2631480f / #2631481a(深色 #ffffff14 / #ffffff24) 悬停与展开态
--dsw-alias-state-success/warn/error/idle-primary —— 四种圆点
--dsw-alias-switch-thumb、--dsw-alias-button-primary-fill —— 设置页开关与主按钮

为什么底色不透明(这是一次线上回修)。 最初照 MenuSurface.module.css 用了壳的菜单材质 --dsw-menu-surface-fill(#f8f9fa94 / #43454a73,45–55% 不透明度)叠 --dsw-menu-backdrop-filter。问题在真实聊天流里才暴露:那一层半透明让底下的工具卡片与文字透上来, 事件行和它背后的内容叠在一起、两边都读不清 —— 截图里的双影就是这么来的;而当 backdrop 模糊没有生效时, 45% 的颜料盖在段落上就只是糊。菜单可以这么薄(它小而往往压在纯色上),一个 340px 宽、500px 高的面板 不行。所以底改成主题的抬升层(不透明),材质感交给圆角 + 描边 + 投影。

为什么最后改用 top layer(第二次回修)。 不透明之后又收到一张截图:空会话页的大字 hero (探索未至之境)压在了面板上,遮住一条事件行。查过壳的样式表与内联样式,壳里没有任何 z-index 超过 1100(菜单 1100、设置 1001、命令面板 100),而面板已经要了 1300 —— 也就是说「比数字」 这条路本身不成立:那个 hero 落在了一个更高的层叠上下文里,靠继续加大 z-index 只会一路内卷。 所以改用浏览器给的唯一确定性手段:popover="manual" + showPopover(),把打开的面板提升到 top layer,它按定义位于所有层叠上下文之上(dialog/弹出层之外无物可压)。同时重置了 popover 的 UA 默认值(inset:0; margin:auto),否则盒子会被拉伸;::backdrop 也置为透明且不拾取点击。 没有 showPopover() 的引擎退回 z-index:1500,两条路都有断言守着。

第三次回修:先猜错,再用真 Chromium 定案。 提升到 top layer 之后面板跑到右上角去了。我第一次 的判断是「坐标不可靠,那就别用坐标」—— 把面板放回 .dms-root 里当兄弟节点,靠 CSS position: absolute; bottom: calc(100% + 8px) 贴住圆点。结果面板居中飘在屏幕中间。

于是写了个最小复现页,用本机 Chrome 的 headless 模式量真值(同一份 CSS、同一套 DOM、真 showPopover()):

组合 量的结果
top layer + position: absolute + CSS 贴圆点 offsetParent: null,left:0;bottom:calc(100% + 8px) 解析成初始包含块的坐标 → 面板飞出屏幕顶部
top layer + CSS anchor positioning(anchor-name/position-anchor) 锚点没解析到,面板落在 top: 0
top layer + position: fixed + 圆点 getBoundingClientRect() 的视口坐标 圆点 x=22, top=466 → 面板 x=22, bottom=458,正好左对齐、底边高 8px

结论:top layer 里没有锚点链,绝对定位会退回初始包含块,所以只能把圆点量成视口坐标再 position: fixed。之前的漂移不是「用了坐标」,而是同时 portal 到了 body —— portal 会换掉 fixed 的包含块。现在面板留在 .dms-root 里、不 portal、不带 react-dom 依赖,坐标在打开那一刻 量一次,两条结论都有断言与变异守着。

面板内部是「滚动区 + 钉住的页脚」:事件再多也不会把「打开状态页」挤到视野外,面板底边永远是一 条干净的线而不是切在半个字上,滚动区用细滚动条提示还有内容。

尺寸跟壳对齐:圆点按钮 28px 高、8px 圆角(Button 的 sm 规格);胶囊用 Tag 的 1px 8px / 11px 字号;弹层 6px 内边距 + 28px 行高;图标 14px 内联 SVG(currentColor, 不依赖任何图标包)。动效只有两处:弹层 140ms 的淡入上浮、刷新时图标旋转,两者都在 prefers-reduced-motion 下关闭。每个 token 都带字面量兜底,老版本壳里也不会变成透明。

样式是一张 dms- 前缀的样式表,每个 document 注入一次(按 data-dsh-service-status 去重)。 「面板必须不透明」「必须进 top layer」「必须重置 UA 内边距」「必须是 fixed 而不是 absolute」 「坐标必须来自圆点的 rect(左对齐、底边高 8px、宽度收进视口)」这五条被写成了断言(见验证一节), 所以下次把底改薄、把 popover 去掉、或者把它改回按祖先定位,套件会直接变红。


六、布局

src/config.ts        纯逻辑:home 解析 / 校验 / 归一 / ConfigStore(原子写、坏文件回退)
src/status.ts        纯逻辑:实体解码 / Atom+RSS 解析 / 聚合 / 缓存 / readFeed + probeFeed
src/index.ts         host 半:三个路由 + loopback 栅栏 + model_status 工具 + 两个可选注入
src/client/index.ts  浏览器半:共享 store、两枚圆点、详情面板、设置页、样式表
cordis.patch.yml     bundle patch:插入一行,按包名解析,无 config
scripts/build.mjs    零依赖构建:stripTypeScriptTypes + 包成 module-loader 工厂
scripts/verify.mjs   328 项校验:真 feed 夹具 + 配置落盘 + 路由/工具/栅栏 + 假 DOM 驱动真 bundle
scripts/fixtures/    线上 Atom 与 RSS 各截 6 条
node scripts/build.mjs     # -> lib/index.js, lib/status.js, lib/config.js, lib/client.js
node scripts/verify.mjs    # 328 checks
pnpm test                  # 两个都跑

typescript 只用 Node 自带的 stripTypeScriptTypes 擦除;构建末尾会真的 import() 一遍 lib/index.js,所以「类型擦除后留下的悬空 import」在构建期就会炸,而不是等到激活。

两个 host 面都是可选注入

export const inject = []            // 不要求任何服务
ctx.inject(['webServer'], …)        // 没有 webserver 就没有三个路由
ctx.inject(['tools'], …)            // 没有 tools 就没有工具

理由和本仓库另一个插件一样:inject 里声明了某个服务,缺它的组合里整个插件都不会激活。 分别 ctx.inject 之后,只有一半能力的组合仍然拿到它能拿到的那一半。


七、验证到什么程度

已验证(node scripts/verify.mjs,328 项全绿)

  • 解析:真 Atom / 真 RSS 两种格式各自 6 条,两边标题、id、分量完全一致;实体解码(十进制、十六进制、 命名实体、未知实体保留、代理对不生成不可用字符);HTML 先解码再去标签的顺序(反过来 23 项会红); CDATA、rel="alternate" 优先、无日期条目丢弃、RFC 822 归一成 ISO。
  • 聚合:全 resolved 的 feed 是「正常」;更新的已恢复事件不会盖掉更旧的未解决事件; monitoring → 降级;认不出的词 → 未知;分量按严重度再按时间排序、重复点名保留最新。
  • 缓存:TTL 内不发请求、超时后发、refresh 绕过、并发共享一次读、Atom 500 时落 RSS、 两边都挂时 staleness 与 error 正确、慢 feed 超时、空条目视为失败; setFeeds 换地址会清缓存且下一次读新地址。
  • 配置:$DSH_HOME 优先级与空白忽略;URL 校验(只收 http/https,拒 ftp/file/javascript/无 scheme/空/非字符串); 去重、上限 8、空列表回退默认并说明原因;缺文件→默认、坏 JSON→默认并记日志、部分坏→修好其余; 保存是原子的(落盘后无 .tmp 残留)、被拒绝的保存一个字节都不写、部分保存保留未提及的字段。
  • 路由:status 200 + no-store + 载荷带 config;HEAD 无 body;POST 405 + Allow; config GET 返回 live/默认/符号化 home/文件路径/上限;config POST 落盘并切换 monitor 的地址; 非法配置 400 且不落盘;坏 JSON 400;PUT 405;非 loopback 的写 403; check 200(真读一次)/非法地址 400/不可达 422/GET 405;?refresh=1 真重读;monitor 抛错 → 500 并记日志。
  • 工具:定义形状(名字、描述、参数 JSON Schema、输出 schema、render)、execute 的结构化返回值、 未解决事件枚举、refresh 生效、render 输出摘要而非 JSON.stringify。
  • host 装配:两个 ctx.inject 各自的 effect 标签、三条精确路由的 kind/path/handler、工具名; 缺 inject 的 context 不抛错而是说清原因。
  • 浏览器半:bundle 通过 __ModuleLoader__ 以包名注册、只注入 slots;三个席位的 slot 名/id/order 逐字断言; 样式只注入一次且含菜单材质、elevation、圆角刻度、开关 thumb、reduced-motion; 真 lib/client.js 在假 DOM + 真 hook runtime 里被驱动:两枚席位共享一次读 → 点开面板读到状态/事件/分量/链接 → 点刷新(断言 ?refresh=1 且面板不关)→ 点面板内部不关 → 点外部关 → Escape 关, 监听器随之释放 → 设置页:两个开关、两个地址输入、关掉一处并保存(断言 POST 体、已保存)→ 保存后那枚圆点当场消失、另一枚还在 → 两处都关掉 → 再打开回来 → 加行/删行/改行 → 「测试」命中 check 路由 → 不可达地址显示失败 → 非法地址保存显示失败 → 恢复默认回填默认地址 → 卸载后清掉定时器与监听器。

变异测试(改坏实现必须变红,20 个全部让套件变红)

变异 套件反应
配置保存吞掉校验失败 ✗ 7 项
配置不再去重 ✗ 2 项
空地址列表静默保留默认 ✗ 1 项
保存配置后不同步给 monitor ✗ 1 项
某条路由跳过栅栏 ✗ 9 项
浏览器半忽略席位开关 ✗ 2 项
设置页开关不改草稿 ✗ 5 项
保存时不带地址列表 ✗ 2 项
把面板底改回半透明材质 ✗ 1 项
去掉面板的 z-index ✗ 1 项
面板不进 top layer(去掉 popover) ✗ 1 项
打开时不调 showPopover() ✗ 1 项
不重置 popover 的 UA 内边距 ✗ 1 项
面板改回 position:absolute(top layer 里没有锚点链) ✗ 1 项
去掉底边 8px 间距 ✗ 1 项
把测量坐标从面板上摘掉 ✗ 3 项
宽度不再收进视口 ✗ 1 项
面板按祖先定位而非视口坐标 ✗ 1 项
去掉单飞 / 失败不标 stale / 解析顺序反了 / 分量去重失效 ✗ 各 1–23 项(上一轮 10 个变异的延续)

线上验证过(不是声称)

  • 第一版(只有状态路由与工具):装进 web profile 后 patchReload: live 激活 host 半, GET /dsh-service-status/status 返回 200 且 overall: operational;另开 agent 会话调用 model_status 拿回完整摘要。
  • 0.2.0 的 host 半:重启后在运行中的 Desktop 里 GET /dsh-service-status/config 返回 200, status 载荷带 config;设置页被真实使用过 —— 现存配置是 seats: { composer: true, footer: false }, 也就是侧边栏那枚在界面上被关掉了,状态接口如实回同样的值。
  • 0.2.0 的 host 半另在真实进程外用同一份 lib/ 对真 feed 跑了一遍:status 200(12 条事件)、 config GET 200(home: $DSH_HOME、2 个地址)、config POST 200(写盘并把新地址交给 monitor)、 改席位后 status 载荷跟着变、check 对真 RSS 返回 35 条事件、非法地址 400、非 loopback 写入 403。
  • 0.2.4 的定位结论是用本机 Chrome headless 量的真值:把 lib/client.js 里的 CSS 数组原样取出来 (不是手抄),配上和客户端一样的坐标算法,在真 Chromium 里 showPopover() 后量矩形 —— 圆点 x=22, top=466,面板 x=22, bottom=458,即左对齐且底边高 8px,ok: true。 同一次实验也量到了反例:top layer 里 position: absolute 的 offsetParent 是 ull, 换成 CSS anchor positioning 则落到 op: 0 —— 所以「用 CSS 贴住圆点」这条路被真实验否掉了。
  • 0.2.1–0.2.3 的三次修复都由真机截图驱动,每一轮都改对了一件、又暴露出下一件: 先是面板半透明、底下工具卡片透上来且压住面板底边;接着是不透明之后空会话 hero 仍压在面板上 (壳里最大 z-index 只有 1100,面板要 1300 也没用);最后是 top layer 生效了、但按测量坐标定位的 面板跑到了右上角。三次的修法分别是「不透明底」「进 top layer」「删掉测量坐标改用 CSS 锚定」, 每一件都有断言 + 变异守着。

没验证(如实说)

  • 真实浏览器里的像素级外观。这次的透明与层叠问题正是真机截图才发现的 —— 假 DOM 能证明 CSS 里写了不透明底和层叠次序(现在有断言),但量不了实际观感。
  • showPopover() 缺失时的退回。没有 top layer 的引擎走 z-index:1500,此时面板仍可能被 某些特殊层压住;当前 Desktop(Chromium 系)一定有,这条只有单测覆盖。
  • 后台标签页恢复刷新。visibilitychange 只在文档重新可见且超过 60s 时补读一次,没有真实后台场景验证。
  • feed 的措辞变化。Status: / Affected components: 是从摘要里正则提的;状态页换文案时分量表会空, 整体状态则退化成「未知」而不是瞎猜。

八、安装(DSH Desktop / profile web)

Desktop 的应用市场装不了本地包,必须走终端 CLI,且 DSH_HOME 要指向 Desktop 的 harness:

$env:DSH_HOME = "$env:APPDATA\dsh-desktop\harness"
dsh plugin --profile web add link:E:\dsh-plugiun\packages\dsh-service-status

装完之后 profile 的 dependencies 出现 dsh-service-status: link:E:\dsh-plugiun\packages\dsh-service-status, dsh.profile.bundles 出现 dsh-service-status,node_modules/dsh-service-status 是指向本目录的 Junction。

首次安装会随 patchReload: live 直接激活 host 半;之后改 host 代码必须重启 Desktop。 浏览器半每次刷新页面重新拉取。配置文件在第一次保存设置时创建。

退路:把 profiles/web/package.json 换回安装前的备份、删掉那个 Junction 与 <harness home>/service-status/ 即可。

授权

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 3705ffd4c2de

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout