dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Long-task audio reminder for DeepSeek Harness — a short "beep" when a sub-agent finishes, a clear "ding" when the whole turn is done and you should come back. Pure browser-side; works with any AI provider.
DSH(DeepSeek Harness)长任务完成提醒:你发出消息不响;子任务(子代理)完成响一声"嘟";整轮任务跑完、该回来操作了,响一声"叮"(附带可选标题闪动 / TTS 播报)。纯浏览器端做提醒,和你用哪家 AI 模型无关。
subagent, subagent_fork, etc.) settling; plays a short beep per completion, with an automatic merge window for rapid consecutive ones.speechSynthesis (off by default) with customizable template — use {time} as a placeholder for the actual duration.dsh plugin add dsh-plugin-task-notify
Restart dsh web and the settings card appears under Settings → Plugins.
lib/ artifacts):
pnpm install && pnpm build
The
lib/directory ships pre-built; only rebuild after modifyingsrc/.
<dsh-home>/profiles/web/node_modules/dsh-plugin-task-notify/
cordis.patch.yml entry to the profile (the inject: [settings] line is required):
- insert:
- id: task-notify
name: dsh-plugin-task-notify
inject: [settings]
config:
enabled: true
thresholdMinutes: 5
⚠️ The
inject: [settings]line is critical. Without it the cordis loader never waits for the settings service, so the host half'sapply()runs but the namespace never registers — and the settings card silently does not appear. See Troubleshooting below.
| Field | Meaning | Default |
|---|---|---|
enabled | Master switch | true |
thresholdMinutes | Turn-end ding threshold (minutes) | 5 |
subReminderEnabled | Sub-task beep switch | true |
mergeMs | Consecutive sub-task merge window (ms) | 500 |
masterVolume | Ding volume, 0..1 | 0.6 |
subVolume | Beep volume, 0..1 | 0.5 |
ttsEnabled | TTS readout (default off) | false |
ttsTemplate | TTS template, {time} = actual duration | 任务完成,用时约 {time} 分钟 |
titleFlash | Title-bar flash on turn end | true |
globalSessions | Monitor all sessions | true |
batchSingleBeep | Single beep per turn for all sub-tasks | false |
runningCalls → short beep. Merge window deduplicates rapid completions; batchSingleBeep makes one beep per whole turn.{time} replaced by actual duration).0.3.0 起同一份产物同时兼容 DSH 桌面端(0.2.0-rc.2 实证);0.3.1 起桌面端
设置界面恢复:设置页 → 内置插件 → "任务提醒" 标签页(settings.plugins.tab
槽位,与官方插件设置页同一代机制),11 项配置在线读写,保存即落
profiles/desktop/cordis.patch.yml 并热重载,提醒器免重启即时生效。桌面端与
web 的差异:
设置页(0.3.1):桌面端客户端运行时没有 settingsScope 服务、设置页也不再
渲染 settings.plugin.item 槽位,所以卡片注册到 0.2.0 的 settings.plugins.tab
槽(设置页"内置插件"分区;官方 shell / agent-loop / web-search 等插件设置页
同为这一代机制)。读取走宿主半 GET /task-notify/api/config(value/base/
revision);保存走 POST /task-notify/api/config —— 宿主半以 settings.update
写入 profile 的 patch 层(configEditor 原子写 + 配置层热重载 + entry 重挂载),
与官方设置页同一条持久化路径;落盘后响应回读新值,设置卡与桌面通知器
同时刷新。手编 profiles/desktop/cordis.patch.yml 仍然有效(形状相同),作为
脚本化/无人值守的配置入口保留:
- id: task-notify
config:
enabled: true
thresholdMinutes: 5
masterVolume: 0.6
(patch 对 config 是整体替换语义,要改的字段写全;0.3.1 后保存走设置页即自动 生成/更新这一段,无需手编。)
volatile 标记(0.3.1):宿主半 Config schema 的 11 个字段全标
.volatile() —— 0.2.0 的 SettingsForms 只为 volatile 字段生成可编辑表单,
这也是设置页能出现/写到本插件的前提。volatile 字段在 0.2.0 运行时解析为
引用对象({ get() }),宿主半与 web 路径消费前统一 resolvePlainConfig
解包(两代运行时行为一致)。
轮次边界:不再读 getSnapshot().running(0.2.0 会话快照无此字段),改订阅
每个会话的 eventSource 事件流(turn/start / turn/end),与
dsh-plugin-whale-pet 同款客户端契约。声音触发规则与 web 一致。
子任务"嘟":以会话行的 parentSessionId 识别子代理会话。字段缺失时该
特性静默降级(不误报主任务"叮")。
装载(与 dsh-plugin-host-monitor 桌面端同款):profile package.json
的 link: 依赖(junction 直连仓库)+ dsh.profile.bundles 登记 + 重启。
包自带 cordis.patch.yml(bundle 层提供 inject: [settings] 与默认值)。
0.1.0-rc.8 client contract (same baseline as dsh-plugin-agent-workflow).0.2.0-rc.2 dual-path support (0.3.0, 2026-10): the desktop
client runtime dropped settingsScope and the plugins-tab slot, and the host
dsh-settings dropped register()/watch(). The host half now capability-probes
ctx.settings.register (0.1.x namespace loop on web; config comes from the
apply(ctx, config) argument on desktop), serves /task-notify/api/config,
and the client half soft-probes settingsScope (declared inject keeps only
services that exist on both runtimes) to pick the web card path or the desktop
eventSource notifier path.sessions.list, running, runningCalls) and standard settings slots.inject() snapshot forever).0.1.2-rc.1 snapshot-shape fix (2026-09): the client notifier previously read snap.runningCalls / snap.turnTimings as flat fields, but on 0.1.2-rc.1 SessionSnapshot only exposes running (running-call tree / turn timings moved into the chat view views.get('chat')?.legacy). Reading the flat field threw TypeError: items is not iterable inside walk → every SessionTracker.adopt() died → trackers= 0 and the whole notifier was silent, with the error repeating on every sync. Fix: added runningCallsOf() / turnTimingsOf() adapters that read views.get('chat')?.legacy?.runningCalls first, fall back to the flat field, and degrade to empty on absence; walk now guards Array.isArray. On 0.1.2-rc.1 the turn-end "叮" works (it only needs running); the sub-agent "嘟" is a silent no-op there because the running-call tree is not reachable through sessions without the chat-view service.Card shows stale values right after Save (controls look like they "did not work" even though the settings file actually updated):
The DSH slot renderer caches each slot entry's inject() result per registration (dsh-client-ui-renderer cachedRootInject, a WeakMap keyed by the entry). The settings card therefore receives whatever the inject closure returned on first render, forever. The 0.2.0 card was injected a one-shot value snapshot, so after Save (which writes via the live scope.set) the card fell back to the frozen snapshot and displayed the pre-save state. Fix (0.2.1): the inject closure hands the card the live bound settings scope instead of a snapshot; the card subscribes to it (scope.subscribe + scope.getSnapshot().value) and re-reads after every settled write, so Save updates the card without a reload.
Settings card does not appear even though the plugin shows as mounted and active in the plugin list:
The cordis loader reads inject from the patch line only, not from the plugin module's export const inject. If the patch line omits inject: [settings], the host half's apply() runs but any async delay (like installSettingsSection) never fires — the settings namespace is never registered, and the card never appears. Fix: add inject: [settings] to the patch line and use synchronous ctx.settings.register() in apply().
MIT © DearMrChai