Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## [Unreleased]

Fixes for current DeepSeek Harness (0.1.5-rc.x); bundle `1.0.7` → `1.0.8`.

- **Fix: the whole web plugin tree failed to boot** (`Failed to load plugins`, the only
thing the GUI rendered) because the bundle required
`@deepseek-ai/dsh-client-runtime/client` — a package DSH dropped after `dsh-web-app`
0.1.1-rc.2. That `require` was a dependency sanity check whose value was never used;
it is gone, and so is the stale `dsh.client.inject` entry that named the same package
(an inject edge pointing at a package that ships no row). The bundle now imports only
`react` from the platform seed table.
- **Fix: the settings page never appeared** on current hosts. `settings.section` is
declared by the settings entry, which applies *after* this plugin, so registering
directly threw `slot "settings.section" is not declared (a parent entry's children
table must declare it)`. Registration now goes through
`ctx.slots.inject('settings.section', …)` — the same call `dsh-plugin-wallpaper-engine`
makes — and falls back to immediate registration on hosts without `slots.inject`.
- Tests: the fake `require` no longer resolves the dropped runtime package, and
`card.test.cjs` covers both the deferred (`slots.inject`) and immediate registration
paths.
- README: compatibility section (requires DSH ≥ 0.1.2; a bundle-level `require` failure
takes the whole plugin tree down, not just this plugin).

## [1.0.0] — 2026-08-22

First open-source release. Behavior equals the locally developed `v1.0.7` bundle.
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ dsh plugin --profile web add github:chidaic/dsh-agent-notify

浏览器端 bundle 变更后**刷新页面即可**(`/plugins/dsh-agent-notify/client.js` 每次读取最新文件;若遇到旧缓存,用 Ctrl+Shift+R 硬刷新)。

## 兼容性

- **要求 DeepSeek Harness ≥ 0.1.2**。`@deepseek-ai/dsh-client-runtime` 是 0.1.1-rc.2 及更早
才有的客户端运行时包,官方从 0.1.2-rc.1 起已移除。本 bundle 不依赖任何客户端运行时包,
只从平台 seed 表取 `react`。
- 早于 0.1.2 的宿主上,bundle 内部对已下线包的 `require` 会抛
`missed the module table`;**该错误会让整棵插件树 boot 失败**,WebUI 只显示
`Failed to load plugins`(不是本插件单独不显示)。
- 设置页通过 `ctx.slots.inject('settings.section', …)` 注册:当前宿主的该 slot 由 settings
entry 声明、比插件更晚 apply,直接 `register()` 会抛 `slot "settings.section" is not
declared`。宿主没有 `slots.inject` 时自动回落到直接注册。

## 配置

无 host 配置。浏览器端设置位于官方设置界面 →「任务提示」页面,持久化于 localStorage。
Expand All @@ -48,7 +60,7 @@ dsh plugin --profile web add github:chidaic/dsh-agent-notify

```
dsh-agent-notify/
├── package.json # dsh.client 声明(inject: dsh-client-runtime, platform: web)
├── package.json # dsh.client 声明(platform: web)
├── cordis.patch.yml # 插件行(bundle patch)
├── LICENSE # MIT
├── CHANGELOG.md
Expand Down
25 changes: 18 additions & 7 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@
window.__ModuleLoader__.load({
id: 'dsh-agent-notify',
factory: (require) => {
// Ensure the declared dependency bundle is registered before we run; the
// dsh.client.inject edge guarantees it is loaded ahead of us.
require('@deepseek-ai/dsh-client-runtime/client')
const React = require('react')
var module = { exports: {} }
var exports = module.exports

/** Bump on every behavioral change; shown in the settings page for diagnosing stale bundles. */
const BUNDLE_VERSION = '1.0.7'
const BUNDLE_VERSION = '1.0.8'
try {
console.log('[dsh-agent-notify] bundle v' + BUNDLE_VERSION + ' loaded')
} catch (error) { /* ignore */ }
Expand Down Expand Up @@ -420,16 +417,30 @@ window.__ModuleLoader__.load({
ctx.on('connection/reset', resetBaseline)
// Register a first-level settings page (Settings → 任务提示), the
// same mechanism the pet plugin uses for Settings → 宠物.
//
// The slot is DECLARED by the settings entry (`children` table), which
// applies after this plugin does, so a direct register() throws
// `slot "settings.section" is not declared (a parent entry's children
// table must declare it)`. `slots.inject(name, cb)` defers the callback
// until the declaration lands — the same call dsh-plugin-wallpaper-engine
// makes. Older hosts without slots.inject keep the immediate path.
if (ctx.slots && typeof ctx.slots.register === 'function') {
try {
const registerSection = () => {
ensureStyle()
const unregister = ctx.slots.register({
return ctx.slots.register({
name: 'settings.section',
id: 'agent-notify',
order: 131,
label: () => '任务提示',
}, SettingsCard)
ctx.effect(() => unregister, 'agent-notify: settings section')
}
try {
if (typeof ctx.slots.inject === 'function') {
ctx.slots.inject('settings.section', registerSection)
} else {
const unregister = registerSection()
ctx.effect(() => unregister, 'agent-notify: settings section')
}
} catch (error) {
console.warn('[dsh-agent-notify] settings section registration failed:', error)
}
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@
"patch": "./cordis.patch.yml"
},
"client": {
"inject": [
"@deepseek-ai/dsh-client-runtime"
],
"platform": "web"
}
},
Expand Down
45 changes: 34 additions & 11 deletions test/card.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,37 @@ function makeListStore() {
}
}

function makeCtx() {
function makeCtx(opts = {}) {
const list = makeListStore()
const sessions = { list, binding: () => undefined, open: () => {} }
const registered = []
const pendingInjects = []
const slots = {
register: (options, component) => {
registered.push({ options, component })
return () => {}
},
}
// On current hosts `settings.section` is declared by the settings entry, which
// applies after this plugin, so the plugin must go through slots.inject and
// wait for the declaration. `inject: false` models an older host instead.
if (opts.inject !== false) {
slots.inject = (name, cb) => { pendingInjects.push({ name, cb }) }
}
const ctx = {
sessions,
slots: {
register: (options, component) => {
registered.push({ options, component })
return () => {}
},
},
slots,
effect: () => () => {},
on: () => () => {},
}
ctx._registered = registered
ctx._pendingInjects = pendingInjects
/** Simulate the declaring entry applying: fire the deferred callbacks. */
ctx._declare = (name) => {
for (const pending of pendingInjects.splice(0)) {
if (pending.name === name) pending.cb()
}
}
return ctx
}

Expand Down Expand Up @@ -121,7 +136,6 @@ function loadBundle(opts = {}) {
vm.runInContext(bundleSrc, dom.getInternalVMContext())
if (captured === null) throw new Error('bundle did not register via __ModuleLoader__.load')
const exportsObj = captured.factory((spec) => {
if (spec === '@deepseek-ai/dsh-client-runtime/client') return {}
if (spec === 'react') return React
throw new Error('unexpected require: ' + spec)
})
Expand All @@ -130,7 +144,7 @@ function loadBundle(opts = {}) {

function harness(opts) {
const h = loadBundle(opts)
const ctx = makeCtx()
const ctx = makeCtx(opts)
h.bundle.apply(ctx)
ctx.sessions.list._set({
phase: 'ready',
Expand All @@ -143,7 +157,16 @@ function harness(opts) {

;(async () => {
console.log('card: rendered settings page interactions')

// Older host: no slots.inject -> registration happens during apply().
const legacy = harness({ inject: false })
check('legacy host registers immediately', legacy.ctx._registered.length === 1)

const { window, ctx } = harness()
check('registration deferred until the slot is declared',
ctx._registered.length === 0 && ctx._pendingInjects.length === 1)
check('deferred inject names settings.section', ctx._pendingInjects[0].name === 'settings.section')
ctx._declare('settings.section')
const entry = ctx._registered[0]
check('section registered', entry !== undefined && entry.options.name === 'settings.section')
check('label is 任务提示', entry.options.label() === '任务提示')
Expand All @@ -165,7 +188,7 @@ function harness(opts) {
check('page has test button',
Array.from(card.querySelectorAll('button')).some((b) => b.textContent.includes('测试通知')))
check('permission label shows granted', card.textContent.includes('已授权'))
check('version line shows v1.0.7', card.textContent.includes('v1.0.7'))
check('version line shows v1.0.8', card.textContent.includes('v1.0.8'))

// Click the test-notification button -> immediate system notification.
const testBtn = Array.from(card.querySelectorAll('button')).find((b) => b.textContent.includes('测试通知'))
Expand Down Expand Up @@ -225,7 +248,7 @@ function harness(opts) {

// Console diagnostic hook: installed, exposes version, test() sends and
// returns a result string (counts as one more notification).
check('debug hook installed', window.__agentNotify !== undefined && window.__agentNotify.version === '1.0.7')
check('debug hook installed', window.__agentNotify !== undefined && window.__agentNotify.version === '1.0.8')
check('debug hook settings', window.__agentNotify.settings().system === 'off')
const beforeHook = notifications.length
const hookResult = window.__agentNotify.test()
Expand Down
3 changes: 2 additions & 1 deletion test/client.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function makeCtx() {
open: (id) => { opens.push(id) },
}
const registered = []
// Deliberately models a host whose slots service has no inject(): the plugin
// must register immediately there (card.test.cjs covers the inject path).
const slots = {
register: (options, component) => {
registered.push({ options, component })
Expand Down Expand Up @@ -128,7 +130,6 @@ function loadBundle(opts = {}) {
window.Notification = FakeNotification

const exportsObj = captured.factory((spec) => {
if (spec === '@deepseek-ai/dsh-client-runtime/client') return {}
if (spec === 'react') return React
throw new Error('unexpected require: ' + spec)
})
Expand Down