diff --git a/CHANGELOG.md b/CHANGELOG.md index 9817e54..6385733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index cf98450..be47cdc 100644 --- a/README.md +++ b/README.md @@ -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。 @@ -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 diff --git a/lib/client.js b/lib/client.js index e397179..81cbc81 100644 --- a/lib/client.js +++ b/lib/client.js @@ -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 */ } @@ -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) } diff --git a/package.json b/package.json index 778ec56..c7d0217 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,6 @@ "patch": "./cordis.patch.yml" }, "client": { - "inject": [ - "@deepseek-ai/dsh-client-runtime" - ], "platform": "web" } }, diff --git a/test/card.test.cjs b/test/card.test.cjs index f866b5c..6146605 100644 --- a/test/card.test.cjs +++ b/test/card.test.cjs @@ -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 } @@ -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) }) @@ -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', @@ -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() === '任务提示') @@ -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('测试通知')) @@ -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() diff --git a/test/client.test.cjs b/test/client.test.cjs index d890a35..76c60b8 100644 --- a/test/client.test.cjs +++ b/test/client.test.cjs @@ -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 }) @@ -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) })