DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Task Tracker — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
T

dsh-task-tracker

Task Tracker

DSH 任务追踪:输入框左侧的任务面板按钮,用独立窗口实时查看任务完成进度,并在任务跑完、出错或需要你选择时发送通知(自己中断的不报)

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:ilxg/dsh-task-tracker#943aaaec6204377533ffdcc89858ba011c8b1e86
README兼容性版本

兼容性与来源证明

Task Tracker 以 dsh-task-tracker 发布,当前版本为 1.0.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/10/2

版本

0.2.0stable
2026/10/2
1.0.0stable
2026/10/2

相关插件

正在加载相关插件…

最新版
1.0.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/10/2
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 productivity-workflow 分类下经过校验的插件。

Acp App@deepseek-ai/dsh-acp-appdsh ACP 配置文件包:基于 dsh-base 的仅限自动化的 JSON-RPC stdio 和进程生命周期管理Experimental Schedule Bundle@deepseek-ai/dsh-experimental-schedule-bundle为 Web 组合添加 Schedule、其任务页面和时间上下文的可选捆绑包Ab Ocrdsh-ab-ocr--- description: "外部 ocr 工具:逐页识别扫描的 PDF 或图像,仅在处理完最后一页后按顺序合并页面,并使用模型确定的标题级别保存为一个 Markdown 文档。" kind: "package-reference" ---Web All@linxin666/dsh-web-allDSH Web UI 全家桶聚合插件:一键安装全部功能插件(task-board / git-graph / pet / remote-web-ui / web-ui-settings / skin-center / community-plugins / compat shim)。compat 桥接层已并入本包(src/client),无需独立 compat npm 包。

README

dsh-task-tracker

给 DeepSeek Harness 的任务追踪插件:输入框左侧多一个按钮,点开是一扇独立的任务窗口, 实时显示任务进度;任务跑完 / 出错 / 需要你选择时弹一条原生 Windows 通知。

┌ 输入框工具行 ────────────────────────────────────────────┐
│ +   [权限] [计划]   ☑ 3/5 ●            …   [模型]  发送  │
│                      ↑ 本插件                             │
└──────────────────────────────────────────────────────────┘

它由两半组成:浏览器半侧(lib/client.js,注册按钮与通知监视器、组装窗口内容)和 host 半侧(lib/window.js,一个只绑 127.0.0.1 的本机服务:开窗、发原生通知、读会话日志)。 两半零外部依赖,没有构建步骤。


功能

1. 输入框左侧的按钮

插槽 conversation.input.left 是 list 类型,官方占用者为空,本插件用新 id = task-tracker 占一格,不会顶掉旁边的「+ / 权限 / 计划」。按钮上直接显示 已完成/总数 徽标,有任务在跑时 多一个脉动圆点。

点击是开关:开着就关、关着就开。(window.open(url, "同名") 对一个已存在的名字会返回 一扇新的空白窗,所以"每次点击都开"会叠出一堆空窗。)

2. 任务窗口:两级列表 + 详情

① 项目列表(每次打开都在这里)
   示例工作区        3 个会话 · 1 个等你回复 · 1 个在跑
   demo-project      2 个会话 · 空闲
   归档项目          4 个会话            ← 单独一个门,点进去才展开
        ↓ 点某个项目
② 该项目的会话列表(‹ 返回项目列表)
   重构导出逻辑…    当前会话 · 运行中 · 已运行 12 秒 · 3/5 已完成
   另一个会话        空闲 · 3 天前
        ↓ 点某一行
③ 详情(当前状态 / Token 用量 / 任务清单),‹ 逐级返回
  • 项目级只显示汇总(N 个会话 · N 个等你回复 · N 个在跑),点进去才列会话;项目内不重复项目名。
  • 每一行都带 token 用量与缓存命中率:项目行是该项目所有会话的合计,会话行是各自的用量。 合计的命中率按 billed token 加权重算,不是把百分比平均。
  • 归档单独成门:按 useWorkspaces() 快照里的 archivedSessionIds 判定,归档会话不与在线会话混排。
  • 会话行 meta 带相对时间,需要区分时再带项目名。
  • 打开时总是回到①:openWindow() 会把位置重置到项目列表;窗口开着的时候位置照常保留。

两类会话不显示(都会挤占列表,而且都不是你发起的对话):

类型判定为什么隐藏
子会话parentId / parentSessionId / origin: 'subagent'工作流派生的子会话可能一次几十个,描述还都一样
空会话会话行的 blank 标记没有首条消息 → 标题回退成目录名,看着像"多出来的项目"

这两类也不会触发系统通知:否则一次 task 工具派生的子 agent 结束就会弹一条「任务已完成」 —— 只有整个任务结束才该弹。两级的计数都不含它们。

会话作用域的投影只属于本页打开的那个会话。看别的会话时:

看的是哪个会话标题Token 用量任务清单 / 进度线
应用当前打开的那个它自己权威投影:总量 / 输入 / 输出 / 缓存命中有
别的会话它自己它自己会话行上的总量与缓存命中(标注「本会话」)没有,只有一行说明

进度线用的是同一个 todo 投影,所以遵守同一条规则:不属于本页打开的会话时,它显示 「任务清单只在打开该会话时可见」,而不是借一个数字。

3. 通知

系统通知标题就是任务状态,正文是会话名与状态:

触发标题说明
会话从「运行中」变成不再运行,且是跑完的任务已完成只对整个会话报一次
同上,但那一轮是自己按停的(不弹)只写进诊断记录,方便回答"为什么没弹"
同上,原因是 error / 其他任务失败 / 任务未跑完出错与"跑了一半停下"用不同文案
出现 pendingInteraction(提问 / 计划待审 / 待授权)需要你的选择同一会话同一事件只报一次

「跑完」和「按停」为什么能分开

插槽快照只给 running 布尔值:useSessionStatus 交出来的就是 { running, pendingInteraction, completionUnread },里面没有原因,所以"我按了停止"和 "它跑完了"在页面上长得一模一样。可用的投影也没有这个信息(sessionStats 只有轮数/步数/耗时, turnOutline 只有提问与回答的预览)。

唯一记录原因的是会话日志:每一轮都以 turn/end { data: { reason: { kind } } } 收尾。 所以 host 半侧读它(POST /turn),客户端只负责按结果决定说不说、怎么说。实际会出现的取值:

completed    跑完了            → 任务已完成
error        出错了            → 任务失败
max-tokens   上下文用尽等停下   → 任务未跑完
aborted      你自己按的停止     → 不播报

读日志的坑:会话日志是追加写的 zstd 流,一次写一个 frame。zstdDecompressSync 一次只解出 第一个 frame(那行会话头),于是整段对话看起来是空的 —— 必须按 frame 魔数切开逐个解。 tools/turn-ends.mjs 把这个技巧做成了一个小工具,可以先把真实取值数出来再信任它。

原因读不到时照样报(日志没落盘、会话不在本机、host 半侧没起来):一律按「任务已完成」播报。 宁可多报一次,也不能把真正的完成悄悄吞掉。

通道

走 host 侧的原生 Windows toast:浏览器 Notification API 在这个桌面构建上「权限 granted、 构造成功、但什么都不进通知中心」(注册表 LastNotificationAddedTime 不更新、onshow 从不触发), 所以由 host 用 PowerShell 5.1 的 WinRT 投影发一条归属本应用 AUMID 的 toast —— 用法是 「临时 .ps1 + 环境变量传 payload」,且不能 detached(detached 启动时 PowerShell 静默失败)。 服务不可用时才回落到浏览器 API。

host 的 /notify 250 ms 只放一条,而一个 tick 里很容易有两个事件(一个会话结束、另一个开始 等你);被拒会重试一次再回落 —— 否则第二条会交给一个根本送不到的通道,静默消失。

4. 任务运行时间

会话详情的「当前状态」里显示这一轮的运行时间:

● 重构导出页                运行中 · 已运行 1 分 23 秒
● 重构导出页                空闲 · 上次运行 2 分 10 秒

running 是布尔值,它不说这一轮什么时候开始的,所以读数由两个来源合起来:

  1. 页面看到的边沿:会话从「不在跑」变成「在跑」的那一刻(一秒 tick 里顺带记录,不新增订阅);
  2. 会话日志的 turn/start:time 是 epoch 毫秒,可以直接和页面时钟比 —— 这一条覆盖了 页面看不见起点的情况(面板打开时任务已经在跑、页面刚刷新过)。

关键在区分这两者:页面第一次看到某个会话时它可能已经跑了十分钟,此时自己起一个秒表会把它 报成「3 秒」。所以运行记录里有一个 certain:

情况显示
看着它开始的(或日志已给出 turn/start)已运行 12 秒
第一次看到时它已经在跑、日志还没回答已运行 ≥12 秒 —— 下限,不是假数
跑完了上次运行 2 分 10 秒(冻结,不再跳动)

时长按人读的方式取舍:12 秒 / 1 分 23 秒 / 2 小时 5 分(过了小时不再报秒)。读数是按会话 id 记的,所以别的会话的详情也有 —— 这一项不像 todos / tokenUsage 那样是"本页打开才有"的投影。


安装

桌面端 profile(~/.dsh/profiles/desktop)由 Electron 独占管理,dsh plugin 会拒绝操作, 所以本插件用一个 junction + 一行 patch 挂载 —— 不动 profile 的 package.json 与 lockfile:

node tools/install.mjs             # 查看状态
node tools/install.mjs --install   # 挂载(幂等;改动前会备份 cordis.patch.yml)
node tools/install.mjs --uninstall # 卸载
node tools/install.mjs --profile web --install   # 其他 profile

安装做两件事:

  1. 建 junction <profile>/node_modules/dsh-task-tracker → 本目录;
  2. 在 <profile>/cordis.patch.yml 末尾追加一段带标记的 insert(两行:包入口 + dsh-task-tracker/window, 后者是一个从未导入过的模块 URL,用来绕过 Node 的 ESM 缓存)。

装完刷新页面即可(F5)。DSH 的 profile 配置是活的:运行中增删该行,boot graph 会跟着变。

若某个 profile 的 pnpm 之后重装依赖,junction 可能被清掉,重跑一次 --install 即可。


本机服务与安全边界

host 半侧只绑 127.0.0.1(17777 起,占用则顺延),端点:

端点作用
GET /ping发现服务;回带客户端心跳、窗口状态、最近一次 toast 结果与回合查询结果
POST /health客户端自报(版本、级别、按钮的 DOM 矩形)
POST /state / GET /state推 / 读窗口快照
POST /nav窗口页把行点击传回来
POST /turn读会话日志:这一轮怎么结束的、什么时候开始的
POST /notify发一条原生 toast
POST /open /close /toggle开关 Edge/Chrome 应用窗口
GET /窗口页

一个监听 loopback 的 HTTP 服务,任何网页都能对它发请求,所以端点做了四道限制 (tools/hostcheck.mjs 里有对应的回归断言,改坏了会红):

限制挡住的攻击
Host 必须是 127.0.0.1 / localhost / [::1](含端口)DNS rebinding:公网域名解析到 127.0.0.1 时,Host 头仍是它自己的名字
Origin 必须是 dsh-app:、null(个别构建下应用页是 opaque origin)或同端口 loopback普通网站直接读 /state(会话标题、项目路径)或伪造 /notify
写端点只接受 POST<img src="http://127.0.0.1:17777/toggle"> 这类 no-cors GET —— 浏览器在这种请求上不发 Origin,只靠 Origin 门是挡不住的
CORS 只回显被接受的那个 origin,并给 vary: origin任意站点读取响应体

另外:/notify 有最小间隔与 512 KB 体积上限;窗口页带 default-src 'none'; frame-ancestors 'none' 的 CSP;toast 用的 PowerShell 脚本每次都在插件自己的私有目录里用随机名生成、用完即删 (放在共享 %TEMP% 里的固定文件名会被同用户进程替换成任意脚本,而它是以 -ExecutionPolicy Bypass 跑的);页面只接受颜色字面量作为 CSS 变量,避免快照让窗口去加载外部资源。


窗口怎么开

窗口由输入框那个格子每秒把一份 view 推给 host 服务(POST /state),窗口页只负责画。 这一条是必需的两半:格子是会话作用域的,回合进行中会被重挂载甚至不再渲染;而窗口页是纯 HTML, 读不到 app 的 store。所以「读快照」和「画窗口」被拆开:格子在自己的每次渲染里把最新快照交给 模块级单例(globalThis.__dshTaskTrackerShared,热更新换实例也不丢),模块级的一秒 tick 读它、组装 view、推给 host,并在同一个 tick 里判定通知。

窗口按可用性降级:

模式机制说明
popup外壳补丁放行的 about:blank#dsh-task-trackerDSH 自己的窗口:同一任务栏条目、随应用退出、可缩放
hosthost 半侧 POST /open + Edge/Chrome --app=未打补丁时的窗口:独立进程的浏览器应用窗口
panel应用内浮层前两者都起不来时的兜底,保证点按钮不会毫无反应

窗口页是哑渲染器:它和面板渲染的是同一份 view(含 mode 与 compact),所以措辞与分段 不会分叉。

可选:让窗口变成「DSH 自己的窗口」

默认情况下 host 拉起的是 Edge/Chrome 的应用窗口(独立进程、独立任务栏分组)。要让它属于 DeepSeek Harness 自己,需要给桌面外壳打一个外科式补丁 —— 可选、每台机器各自决定的增强; 插件本身不依赖它(没打补丁时自动用 Edge 窗口,功能完全一样)。

node tools/shell-patch.mjs --status   # 是否已打、是否有备份、应用是否在运行
node tools/shell-patch.mjs --build    # 只生成 app.asar.patched 并校验,不碰原文件
node tools/shell-patch.mjs --apply    # 替换(需要应用已关闭)
node tools/shell-patch.mjs --revert   # 还原

改了什么:主窗口的 setWindowOpenHandler 原本一律 deny,补丁只放行一个标记 URL about:blank#dsh-task-tracker(并给该窗口设好尺寸/标题/不透明底色);其余策略不变。

为什么低风险:不是重打包整个归档,而是把补丁版 lib/main.js 追加到 asar 末尾,再把 header 里 这一个条目指向它;header JSON 用空格补齐到完全相同的字节长度,于是数据段不移动, 其余条目逐字节不变。替换前后各验证一次,验证失败会自动回滚。

代价:应用更新会覆盖 app.asar,补丁失效 → 重跑一次;失效期间插件自动回落 Edge 窗口。 tools/apply-shell-patch.cmd 与 tools/revert-shell-patch.cmd 是双击即可的包装脚本。


开发与验证

node tools/selfcheck.mjs        # 浏览器半侧:桩 React + 桩 loader + 桩 DOM,加载真 bundle
node tools/hostcheck.mjs        # host 半侧:真起一次本机服务,走完所有端点与门禁
node --check lib/client.js      # 语法检查(window.js 同理)
node tools/turn-ends.mjs        # 数一遍会话日志里 turn/end 的真实取值
node tools/read-diagnostics.mjs # 解码出最新一条诊断记录(--all 看全部)

selfcheck.mjs 覆盖:加载器信封、inject 声明、三处版本一致、按钮注册进 conversation.input.left、 钩子顺序不随数据变化、view schema、不透明调色板、todos/token 分区、POST /state、 通知判定(跑完报一次 / 中断不报 / 子会话与空会话不报 / 四种结束原因各自的文案)、 最新副本接管 tick、重载后不重放旧点击、像真点击那样驱动窗口、被限流的 toast 会重试、 任务运行时间的四种文案与 ≥ 限定。

hostcheck.mjs 额外覆盖:/turn 读真实与合成会话日志(含"报的是最新那一轮"、settled 语义、 session- 前缀的目录回落)、点击回执作废、toast 的落定结果。 --open 会真开一次浏览器窗口再关掉,--notify 会真发一条原生 toast。

诊断记录(dsh-task-tracker.diagnostics.v1)

插件把「这个页面实际发生了什么」写进 localStorage,出问题时可直接从磁盘读出来:

字段含义
appliedAt / versionapply() 在本页运行的时刻与版本
tickOwner哪个副本握着那个一秒 tick。和 version 不一致就说明"新代码在页面里,但没在跑"
entryInvokedAt输入框左侧的按钮组件被框架调用过
monitorRenderedAt通知监视器真的被渲染过
notificationLog最近 6 条通知:kind、at、sessionId、reason、notified、channel、delivered。被故意跳过的中断也在里面,所以"为什么没弹"有答案
lastNotificationAt / lastNotificationChannel / lastNotificationDelivered最近一次通知的时刻、通道(host / browser)、是否真的送进系统
lastTurnReason最近一次从会话日志读到的结束原因
openedAt / openMode任务窗口被打开过,以及用了哪种模式
tickError / renderErrortick 或窗口重绘抛出的异常(正常情况下不出现)

桌面端 GUI 的 localStorage 落在 %APPDATA%\@deepseek-ai\dsh-desktop\Local Storage\leveldb\。 read-diagnostics.mjs 会把最新那条解码成 JSON 打印出来:Chromium 把值写成 UTF-16LE, 所以要按 UTF-16LE 解码再 JSON.parse,否则整条记录既花屏又被截断。

从外部确认它还活着:

curl http://127.0.0.1:17777/ping
# health.version / health.owner / health.left.ids / health.button.found+clickable
# lastToast: 最近一次 toast 的落定结果
# lastTurn:  最近一次回合查询(sessionId / state / reason / startedAt)

改完客户端之后,页面怎么拿到新版本

改了 lib/client.js,宿主会算出新的 rev(sha1 over mtime+ctime+size),/plugins/events 上广播 rebuilt,页面会重新抓取并执行新 bundle。要注意两点:

  1. 旧实例的 setInterval 不会自己消失。apply() 会把现有 tick clearInterval 掉再装上自己的, 最新的副本因此一定接管(比当前 owner 旧的副本拒绝接管);/health.owner 与诊断里的 tickOwner 直接说出现在跑的是哪一份。
  2. 插件自己也会刷新页面:监视器挂载时读 __DSH_BOOT__ 里自己的 bundle URL,比对版本号与本模块版本, 不一致就 location.reload() 一次(sessionStorage 标记防循环)。

lib/window.js 是 host 半侧,Node 按 URL 缓存 ES 模块 —— 改它要重启 DSH 才生效。 这是为什么每一条依赖 host 的行为都必须有降级路径,也是为什么窗口页有一道 selfcheck 门禁。


结构

文件作用
lib/index.js包入口,转出 host 半侧
lib/host.js极薄的再导出:让 dsh-task-tracker/window 与包入口指向同一个 URL
lib/window.jshost 半侧:本机 HTTP 服务(Host/Origin 门禁 + 仅 POST 的写端点)、原生 toast、会话日志读取器(/turn)、窗口与窗口页
lib/client.js浏览器半侧:输入框按钮 + 三态视图 + 不透明调色板 + 一秒 tick(推 view + 判通知)+ 自更新
cordis.patch.yml本插件自己的 bundle patch(供 profile 层栈引用)
tools/install.mjs挂载 / 卸载 / 状态
tools/selfcheck.mjs · tools/hostcheck.mjs浏览器半侧 / host 半侧的离线自检
tools/turn-ends.mjs数会话日志里 turn/end 的真实取值(也是逐 frame 解 zstd 的最小示例)
tools/read-diagnostics.mjs · rev-probe.mjs · scan-cache.mjs · feature-probe.mjs取证:诊断记录 / 页面抓过的 rev / Code Cache / 浏览器能力探测
tools/screenshot.mjs · reload-window.mjs截窗口 / 把刷新键发给窗口(仅诊断)
tools/sessions-probe.mjs · session-detail.mjs · session-scan.mjs · session-search.mjs从磁盘核对会话:列表、单个会话的 cwd 与来源、哪些是空会话、按内容全文检索
tools/shell-patch.mjs · apply-shell-patch.cmd · revert-shell-patch.cmd可选的外壳补丁(原子替换 + 校验 + 自动回滚)
tools/fuse-probe.mjs · fuse-dump.mjs · entry-check.mjs · asar-lib.mjsasar 结构 / Electron fuse / 归档条目核对(补丁链路用)

实现要点

  • 插槽:conversation.input.left(会话作用域 list)放按钮 —— 该 list 的官方占用者为空 (旁边的权限 / 计划是相邻的 single 槽),所以新 id 是加一格而不是顶掉谁; shell.overlay(root 作用域 list)放监视器:占住一个必然渲染的座位、把 root 作用域的 store 快照交给 tick、把「按钮到底在不在」上报给 /health。
  • 钩子顺序是契约:useProjection / useSessionStatus / useSessions 都是 useSyncExternalStore 选择器 hook,调用本身就是 hook。条件调用会让 React 抛 minified #310 并注销整个格子(按钮永久消失),所以这些调用无条件、固定顺序, selfcheck.mjs 有一条专门的顺序断言。
  • 快照与绘制分离:格子每次渲染把快照写进模块级单例,一秒 tick 读它并组装 view。 窗口开着时才 POST /state 推给 host;关掉窗口就不再发。
  • 只有最新的副本拥有 tick:热更新会把新 bundle 装进一个旧副本还在跑的页面里, 该活下来的是状态(导航位置、窗口句柄、通知标记),从来不是定时器。
  • 不透明:应用自己的菜单底色是半透明的,面板此前继承它才会透出后面的内容。插件从当前文档 解析出自己的调色板,把文字/背景/描边/悬停色全部合成到不透明底色上再使用,且不使用 backdrop-filter;窗口页也把这套调色板作为 CSS 变量注入。发丝线只是暗示、不能是高光: 候选色若几乎等于底色、或在深色主题上亮到 >0.85,就换成反向色调的低透明度中性色。
  • 通知去重:等待中的交互用框架自带的唯一 key 去重,并在交互消失时才忘掉它 —— 按时长冷却不够(提示会被反复重新发布,措辞一变就换了键),那正是"提示一直弹"的成因。

已知边界

  • 「中断」的判定依赖 host 半侧:客户端问 POST /turn。host 半侧改了要重启 DSH。 重启前后果不同但都不崩:/turn 不可用 → 结束原因读不到 → 按「已完成」报; 运行时间退化成"从本页第一次看见它起算",并带 ≥ 前缀说明这是下限。
  • 通知需要页面处于打开状态:桌面端关掉窗口后页面仍在(托盘常驻),此时通知照常; 彻底退出应用则不会有人通知你。
  • 独立窗口依赖外壳补丁:没打补丁时 Electron 外壳拒绝 window.open,插件自动改用 host 的 Edge 窗口(功能一样,只是任务栏是另一个条目)。
  • 只在有会话时才有按钮:conversation.input.left 是会话作用域的,新建会话的空态没有输入框, 也就没有按钮。
  • 该桌面壳没有绑定刷新快捷键,客户端更新靠「页面自更新」或手动 F5;应用更新会覆盖 app.asar, 外壳补丁随之失效(失效期间自动回落 Edge 窗口)。

MIT License — 见 LICENSE。